Compare commits

...

3 Commits

Author SHA1 Message Date
Sirttas b32169f433 js editor 2026-06-07 22:37:18 +02:00
Sirttas 023693c4c8 text area 2026-06-07 22:06:53 +02:00
Sirttas 47bd728530 cleanup 2026-06-06 23:52:50 +02:00
17 changed files with 189 additions and 345 deletions
+9 -75
View File
@@ -415,66 +415,6 @@ paths:
$ref: "#/components/schemas/AcquisitionResponse" $ref: "#/components/schemas/AcquisitionResponse"
components: components:
schemas: schemas:
IskRuleClauseResponse:
allOf:
- $ref: "#/components/schemas/RuleClauseResponse"
- type: object
properties:
fromLedgerRef:
type: string
pattern: "[a-z]+(-[a-z]+)*"
toLedgerRef:
type: string
pattern: "[a-z]+(-[a-z]+)*"
required:
- fromLedgerRef
- toLedgerRef
ItemExchangeRuleClauseResponse:
allOf:
- $ref: "#/components/schemas/RuleClauseResponse"
- type: object
properties:
rate:
type: string
enum:
- NONE
- VALUE
- JITA_BUY
- JITA_SELL
- EVE_ESTIMATE
fromLedgerRef:
type: string
pattern: "[a-z]+(-[a-z]+)*"
toLedgerRef:
type: string
pattern: "[a-z]+(-[a-z]+)*"
required:
- fromLedgerRef
- rate
- toLedgerRef
RuleClauseResponse:
discriminator:
propertyName: type
mapping:
ISK: "#/components/schemas/IskRuleClauseResponse"
ITEM_EXCHANGE: "#/components/schemas/ItemExchangeRuleClauseResponse"
oneOf:
- $ref: "#/components/schemas/IskRuleClauseResponse"
- $ref: "#/components/schemas/ItemExchangeRuleClauseResponse"
properties:
type:
type: string
required:
- type
RuleResponse:
type: object
properties:
clauses:
type: array
items:
$ref: "#/components/schemas/RuleClauseResponse"
required:
- clauses
UpdateRuleBookRequest: UpdateRuleBookRequest:
type: object type: object
properties: properties:
@@ -487,14 +427,12 @@ components:
items: items:
type: string type: string
pattern: "[a-z]+(-[a-z]+)*" pattern: "[a-z]+(-[a-z]+)*"
rules: script:
type: object type: string
additionalProperties:
$ref: "#/components/schemas/RuleResponse"
required: required:
- ledgerRefs - ledgerRefs
- name - name
- rules - script
- usedForAcquisitions - usedForAcquisitions
RuleBookResponse: RuleBookResponse:
type: object type: object
@@ -511,15 +449,13 @@ components:
items: items:
type: string type: string
pattern: "[a-z]+(-[a-z]+)*" pattern: "[a-z]+(-[a-z]+)*"
rules: script:
type: object type: string
additionalProperties:
$ref: "#/components/schemas/RuleResponse"
required: required:
- ledgerRefs - ledgerRefs
- name - name
- ruleBookId - ruleBookId
- rules - script
- usedForAcquisitions - usedForAcquisitions
UpdateMainLedgerRequest: UpdateMainLedgerRequest:
type: object type: object
@@ -623,14 +559,12 @@ components:
items: items:
type: string type: string
pattern: "[a-z]+(-[a-z]+)*" pattern: "[a-z]+(-[a-z]+)*"
rules: script:
type: object type: string
additionalProperties:
$ref: "#/components/schemas/RuleResponse"
required: required:
- ledgerRefs - ledgerRefs
- name - name
- rules - script
- usedForAcquisitions - usedForAcquisitions
CreateMainLedgerRequest: CreateMainLedgerRequest:
type: object type: object
+39
View File
@@ -18,6 +18,7 @@
"gemory": "file:", "gemory": "file:",
"loglevel": "^1.8.1", "loglevel": "^1.8.1",
"loglevel-plugin-prefix": "^0.8.4", "loglevel-plugin-prefix": "^0.8.4",
"monaco-editor": "^0.55.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"sortablejs": "^1.15.7", "sortablejs": "^1.15.7",
"vue": "^3.3.4", "vue": "^3.3.4",
@@ -866,6 +867,13 @@
"undici-types": ">=7.24.0 <7.24.7" "undici-types": ">=7.24.0 <7.24.7"
} }
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/web-bluetooth": { "node_modules/@types/web-bluetooth": {
"version": "0.0.21", "version": "0.0.21",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
@@ -1585,6 +1593,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dompurify": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -2265,6 +2282,18 @@
"url": "https://github.com/sponsors/sxzz" "url": "https://github.com/sponsors/sxzz"
} }
}, },
"node_modules/marked": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -2330,6 +2359,16 @@
"pathe": "^2.0.1" "pathe": "^2.0.1"
} }
}, },
"node_modules/monaco-editor": {
"version": "0.55.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
"license": "MIT",
"dependencies": {
"dompurify": "3.2.7",
"marked": "14.0.0"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+1
View File
@@ -20,6 +20,7 @@
"gemory": "file:", "gemory": "file:",
"loglevel": "^1.8.1", "loglevel": "^1.8.1",
"loglevel-plugin-prefix": "^0.8.4", "loglevel-plugin-prefix": "^0.8.4",
"monaco-editor": "^0.55.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"sortablejs": "^1.15.7", "sortablejs": "^1.15.7",
"vue": "^3.3.4", "vue": "^3.3.4",
+2 -1
View File
@@ -12,7 +12,8 @@ const modelValue = defineModel({ default: false });
<style scoped> <style scoped>
@reference "@/style.css"; @reference "@/style.css";
input:checked ~ span:last-child { input:checked ~ span:last-child {
--tw-translate-x: 1.25rem; transform: translateX(1.25rem);
} }
</style> </style>
+3 -33
View File
@@ -71,14 +71,8 @@ export interface CreateRuleBookRequest {
'name': string; 'name': string;
'usedForAcquisitions': boolean; 'usedForAcquisitions': boolean;
'ledgerRefs': Array<string>; 'ledgerRefs': Array<string>;
'rules': { [key: string]: RuleResponse; }; 'script': string;
} }
export interface IskRuleClauseResponse extends RuleClauseResponse {
'fromLedgerRef': string;
'toLedgerRef': string;
}
export interface IskTransferResponse extends TransferResponse { export interface IskTransferResponse extends TransferResponse {
'fromLedgerId': string; 'fromLedgerId': string;
'toLedgerId': string; 'toLedgerId': string;
@@ -88,22 +82,6 @@ export interface ItemBalanceResponse {
'typeId': number; 'typeId': number;
'quantity': number; 'quantity': number;
} }
export interface ItemExchangeRuleClauseResponse extends RuleClauseResponse {
'rate': ItemExchangeRuleClauseResponseRateEnum;
'fromLedgerRef': string;
'toLedgerRef': string;
}
export const ItemExchangeRuleClauseResponseRateEnum = {
None: 'NONE',
Value: 'VALUE',
JitaBuy: 'JITA_BUY',
JitaSell: 'JITA_SELL',
EveEstimate: 'EVE_ESTIMATE',
} as const;
export type ItemExchangeRuleClauseResponseRateEnum = typeof ItemExchangeRuleClauseResponseRateEnum[keyof typeof ItemExchangeRuleClauseResponseRateEnum];
export interface ItemTransferResponse extends TransferResponse { export interface ItemTransferResponse extends TransferResponse {
'fromLedgerId': string; 'fromLedgerId': string;
'toLedgerId': string; 'toLedgerId': string;
@@ -125,15 +103,7 @@ export interface RuleBookResponse {
'name': string; 'name': string;
'usedForAcquisitions': boolean; 'usedForAcquisitions': boolean;
'ledgerRefs': Array<string>; 'ledgerRefs': Array<string>;
'rules': { [key: string]: RuleResponse; }; 'script': string;
}
/**
* @type RuleClauseResponse
*/
export type RuleClauseResponse = { type: 'ISK' } & IskRuleClauseResponse | { type: 'ITEM_EXCHANGE' } & ItemExchangeRuleClauseResponse;
export interface RuleResponse {
'clauses': Array<RuleClauseResponse>;
} }
export interface SetCharacterRuleBookRequest { export interface SetCharacterRuleBookRequest {
'ruleBookId': string; 'ruleBookId': string;
@@ -162,7 +132,7 @@ export interface UpdateRuleBookRequest {
'name': string; 'name': string;
'usedForAcquisitions': boolean; 'usedForAcquisitions': boolean;
'ledgerRefs': Array<string>; 'ledgerRefs': Array<string>;
'rules': { [key: string]: RuleResponse; }; 'script': string;
} }
/** /**
+8 -7
View File
@@ -1,12 +1,10 @@
import { import {
BalanceResponse, BalanceResponse,
CombinedLedgerResponse, CombinedLedgerResponse,
CombinedLedgerResponseTypeEnum,
CreateCombinedLedgerRequest, CreateCombinedLedgerRequest,
CreateMainLedgerRequest, CreateMainLedgerRequest,
LedgerResponseTypeEnum, LedgerResponse,
MainLedgerResponse, MainLedgerResponse,
MainLedgerResponseTypeEnum,
TransactionResponse, TransactionResponse,
UpdateCombinedLedgerRequest, UpdateCombinedLedgerRequest,
UpdateMainLedgerRequest UpdateMainLedgerRequest
@@ -16,11 +14,14 @@ import {computed, ref, triggerRef} from "vue";
import {ledgerApi, transactionApi} from "@/mammon"; import {ledgerApi, transactionApi} from "@/mammon";
import {useRouteParams} from "@vueuse/router"; import {useRouteParams} from "@vueuse/router";
export const LedgerTypes = LedgerResponseTypeEnum; export const LedgerTypes = {
Main: 'MAIN',
Combined: 'COMBINED',
};
export type LedgerType = LedgerResponseTypeEnum; export type LedgerType = LedgerResponse['type'];
export type MainLedger = MainLedgerResponse & {type: MainLedgerResponseTypeEnum} export type MainLedger = MainLedgerResponse
export type CombinedLedger = CombinedLedgerResponse & {type: CombinedLedgerResponseTypeEnum} export type CombinedLedger = CombinedLedgerResponse
export type Ledger = MainLedger | CombinedLedger; export type Ledger = MainLedger | CombinedLedger;
export const systemLedgerRef = 'system'; export const systemLedgerRef = 'system';
+6
View File
@@ -23,6 +23,12 @@ const mammonAxiosInstance = axios.create({
}) })
logResource(mammonAxiosInstance) logResource(mammonAxiosInstance)
export const fetchScriptDefinitions = (): Promise<string> =>
mammonAxiosInstance.get<string>('rule-books/script-definitions', {
responseType: 'text',
headers: {Accept: 'text/plain'},
}).then(response => response.data);
export const ledgerApi = new LedgerApi(undefined, mammonUrl, mammonAxiosInstance); export const ledgerApi = new LedgerApi(undefined, mammonUrl, mammonAxiosInstance);
export const transactionApi = new TransactionApi(undefined, mammonUrl, mammonAxiosInstance); export const transactionApi = new TransactionApi(undefined, mammonUrl, mammonAxiosInstance);
export const characterApi = new CharacterApi(undefined, mammonUrl, mammonAxiosInstance); export const characterApi = new CharacterApi(undefined, mammonUrl, mammonAxiosInstance);
+1 -1
View File
@@ -88,7 +88,7 @@ watch(useRoute(), async route => {
<div class="flex flex-wrap items-center mb-2 mt-2"> <div class="flex flex-wrap items-center mb-2 mt-2">
<div class="me-2" v-for="ref in ledgerRefs" :ref="ref"> <div class="me-2" v-for="ref in ledgerRefs" :ref="ref">
<span class="me-1">{{ref}}:</span> <span class="me-1">{{ref}}:</span>
<LedgerSelect :ledgers="ledgersToUse" :modelValue="bindings[ref] ?? systemLedger" @update:modelValue="value => bindings[ref] = value" /> <LedgerSelect :ledgers="ledgersToUse" :modelValue="bindings[ref] ?? systemLedger" @update:modelValue="value => { if (value) bindings[ref] = value }" />
</div> </div>
</div> </div>
</div> </div>
+22 -30
View File
@@ -3,15 +3,16 @@ import {useRoute, useRouter} from "vue-router";
import {ref, watch} from "vue"; import {ref, watch} from "vue";
import {useDebounceFn} from "@vueuse/core"; import {useDebounceFn} from "@vueuse/core";
import log from "loglevel"; import log from "loglevel";
import {activityTypes, RuleInput, Rules, useRuleBooksStore} from "@/rules"; import {ScriptEditor, useRuleBooksStore} from "@/rules";
import {PlusIcon, TrashIcon} from "@heroicons/vue/24/outline"; import {PlusIcon, TrashIcon} from "@heroicons/vue/24/outline";
import {routeNames} from "@/routes"; import {routeNames} from "@/routes";
import {Dropdown} from "@/components"; import {SliderCheckbox} from "@/components";
const ruleBookId = ref<string>(); const ruleBookId = ref<string>();
const name = ref<string>(''); const name = ref<string>('');
const usedForAcquisitions = ref<boolean>(false);
const ledgerRefs = ref<string[]>([]); const ledgerRefs = ref<string[]>([]);
const rules = ref<Rules>({}); const script = ref<string>('');
const {findById, create, update, refresh} = useRuleBooksStore(); const {findById, create, update, refresh} = useRuleBooksStore();
const router = useRouter(); const router = useRouter();
@@ -20,16 +21,18 @@ const save = async () => {
if (!ruleBookId.value) { if (!ruleBookId.value) {
const created = await create({ const created = await create({
name: name.value, name: name.value,
usedForAcquisitions: usedForAcquisitions.value,
ledgerRefs: ledgerRefs.value, ledgerRefs: ledgerRefs.value,
rules: rules.value script: script.value
}) })
await router.push({ name: routeNames.editRuleBook, params: {ruleBookId: created.ruleBookId}}) await router.push({ name: routeNames.editRuleBook, params: {ruleBookId: created.ruleBookId}})
} else { } else {
await update(ruleBookId.value, { await update(ruleBookId.value, {
name: name.value, name: name.value,
usedForAcquisitions: usedForAcquisitions.value,
ledgerRefs: ledgerRefs.value, ledgerRefs: ledgerRefs.value,
rules: rules.value script: script.value
}) })
} }
} }
@@ -57,14 +60,16 @@ watch(useRoute(), async route => {
ruleBookId.value = id; ruleBookId.value = id;
name.value = ruleBook?.name ?? ''; name.value = ruleBook?.name ?? '';
ledgerRefs.value = [...ruleBook?.ledgerRefs]; usedForAcquisitions.value = ruleBook?.usedForAcquisitions ?? false;
rules.value = {...ruleBook?.rules}; // TODO fully clone rules ledgerRefs.value = [...(ruleBook?.ledgerRefs ?? [])];
script.value = ruleBook?.script ?? '';
log.info('Loaded rule book:', ruleBook); log.info('Loaded rule book:', ruleBook);
} else { } else {
ruleBookId.value = undefined; ruleBookId.value = undefined;
name.value = ''; name.value = '';
usedForAcquisitions.value = false;
ledgerRefs.value = []; ledgerRefs.value = [];
rules.value = {}; script.value = '';
log.info('No rule book to load'); log.info('No rule book to load');
} }
}, { immediate: true }) }, { immediate: true })
@@ -76,12 +81,16 @@ watch(useRoute(), async route => {
<div class="flex grow border-b-1"> <div class="flex grow border-b-1">
Name: Name:
<input class="mb-2 ms-2" type="text" v-model="name" /> <input class="mb-2 ms-2" type="text" v-model="name" />
<label class="flex items-center ms-2 mb-2">
<SliderCheckbox class="me-2" v-model="usedForAcquisitions" />
Used for acquisitions
</label>
</div> </div>
<div class="border-b-1"> <div class="border-b-1">
Ledgers References: Ledgers References:
<div class="flex flex-wrap items-center"> <div class="flex flex-wrap items-center mt-2">
<div class="flex items-center mb-2" v-for="(ledgerRef, index) in ledgerRefs" :key="index"> <div class="flex items-center mb-2" v-for="(ledgerRef, index) in ledgerRefs" :key="index">
<input class="me-1" type="text" :value="ledgerRefs[index]" @input="updateLedgerRef(index, ($event.target as HTMLInputElement).value)" /> <input class="me-1" type="text" :value="ledgerRef" @input="updateLedgerRef(index, ($event.target as HTMLInputElement).value)" />
<button class="btn-icon me-2" @click="removeLedgerRef(index)"><TrashIcon /></button> <button class="btn-icon me-2" @click="removeLedgerRef(index)"><TrashIcon /></button>
</div> </div>
<div class="flex items-center mb-2"> <div class="flex items-center mb-2">
@@ -89,14 +98,9 @@ watch(useRoute(), async route => {
</div> </div>
</div> </div>
</div> </div>
<div class="flex flex-col grow border-b-1" v-for="activityType in activityTypes" :key="activityType.key"> <div class="flex flex-col grow border-b-1">
<Dropdown :inline="true" :autoClose="false" class="rule-dropdown"> Script:
<template #button> <ScriptEditor class="mt-2 mb-2" v-model="script" />
<span>{{ activityType.name }}</span>
</template>
<RuleInput :ledgerRefs="ledgerRefs" :activityType="activityType.key" v-model="rules[activityType.key]" />
</Dropdown>
</div> </div>
</div> </div>
<div class="mt-2 justify-end flex"> <div class="mt-2 justify-end flex">
@@ -106,15 +110,3 @@ watch(useRoute(), async route => {
</div> </div>
</div> </div>
</template> </template>
<style scoped>
@reference "@/style.css";
.rule-dropdown :deep(>button) {
@apply bg-slate-800 hover:bg-slate-800 border-none flex items-center w-full;
}
.rule-dropdown.dropdown-open :deep(>button) {
@apply bg-slate-800 rounded-b-none;
}
</style>
-26
View File
@@ -1,26 +0,0 @@
<script setup lang="ts">
import {systemLedger, systemLedgerRef} from "@/ledger";
interface Props {
ledgerRefs: string[];
}
defineProps<Props>()
const ledgerRef = defineModel<string>();
</script>
<template>
<select v-model="ledgerRef" :class="{'system-ledger': ledgerRef === systemLedgerRef}">
<option v-for="l in ledgerRefs" :key="l" :value="l" :class="{'system-ledger': l === systemLedgerRef}">{{ l === systemLedgerRef ? systemLedger.name : l }}</option>
</select>
</template>
<style scoped>
@reference "@/style.css";
.system-ledger {
@apply text-emerald-400;
}
</style>
-53
View File
@@ -1,53 +0,0 @@
<script setup lang="ts">
import {ItemExchangeRuleClauseResponse, ItemExchangeRuleClauseResponseRateEnum, RuleClauseResponse} from "@/generated/mammon";
import {computed, watch} from "vue";
import {systemLedgerRef} from "@/ledger";
import {ratesTypes} from "@/rules/rules.ts";
import LedgerRefSelect from "./LedgerRefSelect.vue";
interface Props {
ledgerRefs: string[];
hasRate: boolean;
}
const props = defineProps<Props>()
const rule = defineModel<RuleClauseResponse>({ default: {
type: 'ITEM_EXCHANGE',
rate: ItemExchangeRuleClauseResponseRateEnum.None,
fromLedgerRef: systemLedgerRef,
toLedgerRef: systemLedgerRef,
}});
// Only item-exchange clauses carry a rate; narrow for the rate <select>.
const itemRule = computed(() => rule.value as ItemExchangeRuleClauseResponse)
const ledgerRefsWithSystem = computed<string[]>(() => [systemLedgerRef, ...props.ledgerRefs])
watch(ledgerRefsWithSystem, (newVal, oldVal) => {
if (newVal.length !== oldVal.length) {
return;
}
if (rule.value.fromLedgerRef && rule.value.fromLedgerRef !== systemLedgerRef) {
rule.value.fromLedgerRef = newVal[oldVal.findIndex(v => v === rule.value.fromLedgerRef)]
}
if (rule.value.toLedgerRef && rule.value.toLedgerRef !== systemLedgerRef) {
rule.value.toLedgerRef = newVal[oldVal.findIndex(v => v === rule.value.toLedgerRef)]
}
})
</script>
<template>
From:
<LedgerRefSelect class="me-2 grow" v-model="rule.fromLedgerRef" :ledger-refs="ledgerRefsWithSystem"/>
To:
<LedgerRefSelect class="me-2 grow" v-model="rule.toLedgerRef" :ledger-refs="ledgerRefsWithSystem"/>
<template v-if="hasRate">
At:
<select class="me-2 grow" v-model="itemRule.rate">
<option v-for="rateType in ratesTypes" :key="rateType.key" :value="rateType.key">{{ rateType.name }}</option>
</select>
</template>
</template>
-79
View File
@@ -1,79 +0,0 @@
<script setup lang="ts">
import {ItemExchangeRuleClauseResponseRateEnum, RuleClauseResponse, RuleResponse} from "@/generated/mammon";
import RuleClauseInput from "@/rules/RuleClauseInput.vue";
import {computed, useTemplateRef} from "vue";
import {Bars4Icon, PlusIcon, TrashIcon} from '@heroicons/vue/24/outline';
import {useSortable} from "@vueuse/integrations/useSortable";
import {systemLedgerRef} from "@/ledger";
import {ActivityType, activityTypeHasRate} from "@/rules/rules.ts";
interface Props {
ledgerRefs: string[];
activityType: ActivityType;
}
const props = defineProps<Props>()
const hasRate = computed<boolean>(() => activityTypeHasRate(props.activityType));
const rule = defineModel<RuleResponse>({default: {clauses:[]}});
const clauses = computed<RuleClauseResponse[]>({
get: () => rule.value && rule.value.clauses ? rule.value.clauses : [],
set: value => rule.value = {clauses: value}
})
const addClause = () => {
const clause: RuleClauseResponse = hasRate.value
? {type: 'ITEM_EXCHANGE', rate: ItemExchangeRuleClauseResponseRateEnum.None, fromLedgerRef: systemLedgerRef, toLedgerRef: systemLedgerRef}
: {type: 'ISK', fromLedgerRef: systemLedgerRef, toLedgerRef: systemLedgerRef};
clauses.value = [...clauses.value, clause]
}
const setClause = (index: number, clause?: RuleClauseResponse) => {
if (!clause) {
return;
}
clauses.value = clauses.value.with(index, clause)
}
const removeClause = (index: number) => {
clauses.value = clauses.value.toSpliced(index, 1)
}
const sortableContainer = useTemplateRef('sortable-container')
useSortable(sortableContainer, clauses, { handle: '.sortable-handle'});
</script>
<template>
<div class="flex-col">
<div ref="sortable-container" class="flex-col">
<div class="flex items-end gap-2 mt-2" v-for="(clause, index) in clauses" :key="index">
<span class="sortable-handle flex">
<Bars4Icon class="w-6"/>
</span>
<RuleClauseInput :ledgerRefs="ledgerRefs" :hasRate="hasRate" :modelValue="clause" @update:modelValue="v => setClause(index, v)" />
<button class="btn-icon" @click="removeClause(index)"><TrashIcon /></button>
</div>
</div>
<div class="flex justify-end mb-2 mt-2">
<button class="btn-icon" @click="addClause"><PlusIcon /></button>
</div>
</div>
</template>
<style scoped>
@reference "@/style.css";
.sortable-handle {
@apply cursor-grab;
}
.sortable-chosen {
@apply cursor-grabbing;
}
.sortable-chosen .sortable-handle {
@apply cursor-grabbing;
}
</style>
+81
View File
@@ -0,0 +1,81 @@
<script setup lang="ts">
import * as monaco from 'monaco-editor';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
import {onBeforeUnmount, onMounted, ref, watch} from 'vue';
import {fetchScriptDefinitions} from '@/mammon';
(self as unknown as { MonacoEnvironment: { getWorker(workerId: string, label: string): Worker } }).MonacoEnvironment = {
getWorker(_workerId: string, label: string) {
if (label === 'typescript' || label === 'javascript') {
return new tsWorker();
}
return new editorWorker();
}
};
let extraLibLoaded = false;
const loadScriptDefinitions = async () => {
if (extraLibLoaded) {
return;
}
try {
const definitions = await fetchScriptDefinitions();
monaco.typescript.javascriptDefaults.addExtraLib(definitions, 'ts:rule-runner.d.ts');
extraLibLoaded = true;
} catch {
// type definitions are optional — the editor still works without autocomplete
}
};
const model = defineModel<string>({default: ''});
const container = ref<HTMLElement>();
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
onMounted(async () => {
await loadScriptDefinitions();
if (!container.value) {
return;
}
editor = monaco.editor.create(container.value, {
value: model.value,
language: 'javascript',
theme: 'vs-dark',
automaticLayout: true,
minimap: {enabled: false},
scrollBeyondLastLine: false,
fontSize: 13,
tabSize: 2,
});
editor.onDidChangeModelContent(() => {
const value = editor!.getValue();
if (value !== model.value) {
model.value = value;
}
});
});
watch(model, value => {
if (editor && value !== editor.getValue()) {
editor.setValue(value ?? '');
}
});
onBeforeUnmount(() => editor?.dispose());
</script>
<template>
<div ref="container" class="script-editor"></div>
</template>
<style scoped>
.script-editor {
width: 100%;
height: 24rem;
}
</style>
+1 -1
View File
@@ -1,3 +1,3 @@
export * from "./rules"; export * from "./rules";
export {default as RuleInput} from './RuleInput.vue'; export {default as ScriptEditor} from './ScriptEditor.vue';
+3 -29
View File
@@ -2,39 +2,13 @@ import {characterRuleBookApi, ruleBookApi} from "@/mammon";
import { import {
CharacterRuleBookResponse, CharacterRuleBookResponse,
CreateRuleBookRequest, CreateRuleBookRequest,
ItemExchangeRuleClauseResponseRateEnum,
RuleBookResponse, RuleBookResponse,
RuleResponse,
SetCharacterRuleBookRequest SetCharacterRuleBookRequest
} from "@/generated/mammon"; } from "@/generated/mammon";
import {defineStore} from "pinia"; import {defineStore} from "pinia";
import {ref, triggerRef} from "vue"; import {ref, triggerRef} from "vue";
export const activityTypes = { export type RuleBook = RuleBookResponse;
itemBought: {key: "ITEM_BOUGHT", name: "Item Bought"},
itemSold: {key: "ITEM_SOLD", name: "Item Sold"},
itemAcquiredManually: {key: "ITEM_ACQUIRED_MANUALLY", name: "Item Acquired Manually"},
itemConsumedManually: {key: "ITEM_CONSUME_MANUALLY", name: "Item Consumed Manually"},
bountyEarned: {key: "BOUNTY_EARNED", name: "Bounty Earned"},
// itemManufactured: {id: "ITEM_MANUFACTURED", name: "Item Manufactured"}
} as const;
export type Activity = { key: ActivityType, name: string }
export type ActivityType = typeof activityTypes[keyof typeof activityTypes]['key'];
export type Rules = { [key: ActivityType]: RuleResponse; };
export type RuleBook = RuleBookResponse & { rules: Rules }
export const activityTypeHasRate = (key: ActivityType): boolean => key !== activityTypes.bountyEarned.key;
export const ratesTypes = {
None: {key: "NONE", name: "0 ISK"},
Value: {key: "VALUE", name: "Value"},
JitaBuy: {key: "JITA_BUY", name: "Jita Buy Order"},
JitaSell: {key: "JITA_SELL", name: "Jita Sell Order"},
EveEstimate: {key: "EVE_ESTIMATE", name: "Eve Estimate"},
} as const;
export type Rate = { key: ItemExchangeRuleClauseResponseRateEnum, name: string }
export const useRuleBooksStore = defineStore('rule-books', () => { export const useRuleBooksStore = defineStore('rule-books', () => {
const ruleBooks = ref<RuleBook[]>([]); const ruleBooks = ref<RuleBook[]>([]);
@@ -59,7 +33,7 @@ export const useRuleBooksStore = defineStore('rule-books', () => {
const create = (ruleBook: CreateRuleBookRequest) => ruleBookApi.createRuleBook(ruleBook).then(response => addRuleBook(response.data)); const create = (ruleBook: CreateRuleBookRequest) => ruleBookApi.createRuleBook(ruleBook).then(response => addRuleBook(response.data));
const update = (ruleBookId: string, ruleBook: CreateRuleBookRequest) => ruleBookApi.updateRuleBook(ruleBookId, ruleBook).then(response => replaceRuleBook(response.data)); const update = (ruleBookId: string, ruleBook: CreateRuleBookRequest) => ruleBookApi.updateRuleBook(ruleBookId, ruleBook).then(response => replaceRuleBook(response.data));
const refresh = () => ruleBookApi.findAllRuleBooks().then(response => ruleBooks.value = response.data as RuleBook[]); const refresh = () => ruleBookApi.findAllRuleBooks().then(response => ruleBooks.value = response.data);
refresh(); refresh();
@@ -68,7 +42,7 @@ export const useRuleBooksStore = defineStore('rule-books', () => {
export const findCharacterRuleBookByCharacterId = (characterId: number): Promise<CharacterRuleBookResponse> => characterRuleBookApi.findCharacterRuleBookByCharacterId(characterId) export const findCharacterRuleBookByCharacterId = (characterId: number): Promise<CharacterRuleBookResponse> => characterRuleBookApi.findCharacterRuleBookByCharacterId(characterId)
.then(response => response.data) .then(response => response.data)
.catch(() => ({characterId, rules: {}})); .catch(() => ({characterId, ruleBookId: '', bindings: {}}));
export const setCharacterRuleBookForCharacter = (characterId: number, ruleBook: SetCharacterRuleBookRequest): Promise<CharacterRuleBookResponse> => characterRuleBookApi.setCharacterRuleBookForCharacter(characterId, ruleBook) export const setCharacterRuleBookForCharacter = (characterId: number, ruleBook: SetCharacterRuleBookRequest): Promise<CharacterRuleBookResponse> => characterRuleBookApi.setCharacterRuleBookForCharacter(characterId, ruleBook)
.then(response => response.data); .then(response => response.data);
+4 -3
View File
@@ -1,14 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import {Transfer, TransferTypes} from "@/transaction/transaction.ts"; import {TransferTypes} from "@/transaction/transaction.ts";
import {LedgerLabel, systemLedger, useLedgersStore} from "@/ledger"; import {LedgerLabel, systemLedger, useLedgersStore} from "@/ledger";
import {getMarketType, IskLabel, MarketTypeLabel} from "@/market"; import {getMarketType, IskLabel, MarketTypeLabel} from "@/market";
import {computedAsync} from "@vueuse/core"; import {computedAsync} from "@vueuse/core";
import {TransferResponse} from "@/generated/mammon";
type TransferWithValue = Transfer & { marketTypeId: number; }; type TransferWithValue = TransferResponse & { marketTypeId: number; };
interface Props { interface Props {
transfers?: Transfer[] transfers?: TransferResponse[]
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
+6 -4
View File
@@ -1,5 +1,7 @@
import {TransactionResponseTransfersInner, TransferResponseTypeEnum} from "@/generated/mammon"; import {TransferResponse} from "@/generated/mammon";
export const TransferTypes = TransferResponseTypeEnum; export const TransferTypes = {
export type TransferType = TransferResponseTypeEnum; Isk: 'ISK',
export type Transfer = TransactionResponseTransfersInner; Item: 'ITEM',
} as const;
export type TransferType = TransferResponse['type'];