Skip to content

Commit

Permalink
restructure builder website to use composition better
Browse files Browse the repository at this point in the history
  • Loading branch information
fhnaumann committed Feb 12, 2024
1 parent 92d1b0e commit 20f2195
Show file tree
Hide file tree
Showing 17 changed files with 729 additions and 169 deletions.
423 changes: 423 additions & 0 deletions builder_website/challenges_schema.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion builder_website/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import { useConfigStore } from './main';
import Ajv from 'ajv'
import challengesSchema from '@/assets/challenges_schema.json';
const ajv = new Ajv({allErrors: true, strict: true})
const ajv = new Ajv({allErrors: true, strict: 'log'})
const store = useConfigStore()
const toast = useToast()
Expand Down
139 changes: 85 additions & 54 deletions builder_website/src/components/collectableGoal.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,118 @@
import { useActivatableGoal } from "./activatableGoal";
import { computed, ref, toRefs, watch } from "vue";
import { computed, ref, toRaw, toRefs, watch } from "vue";
import { useConfigStore } from '@/main';
//import { DataRow, useLoadableDataRow } from "./loadableDataRow";
import type { DataRow } from "./loadableDataRow";
import { useLoadableDataRow } from "./loadableDataRow";
import type { CollectableEntryConfig, GoalName } from "./model/goals";
import type { BaseGoalConfig, CollectableDataConfig, CollectableEntryConfig, GoalName, GoalPathSplitKey, GoalsConfig } from "./model/goals";
import type { RandomEffectPunishmentConfig } from "./model/punishments";
import type { Model } from "./model/model";
import { useValidator } from "./validator";

export interface AccessOperation {
getSelectedData: (model: Model) => CollectableEntryConfig[],
setSelectedData: (model: Model, newSelectedData: CollectableEntryConfig[]) => void,
getSelectAllData: (model: Model) => boolean,
setSelectAllData: (model: Model, newSelectAllData: boolean) => void
}

export function useCollectableGoal(goalName: GoalName, pathSplit: string, rawLoadedDataFile: string) {
export function useCollectableGoal(accessOperation: AccessOperation, allData: DataRow[], defaultSelectedData: CollectableEntryConfig[], defaultSelectAllData: boolean) {
const model = useConfigStore().model
const validator = useValidator()

const store = useConfigStore().model
const selectedData = ref<CollectableEntryConfig[]>([])
watch(selectedData, newSelectedData => {
console.log("triggered watch", newSelectedData)
accessOperation.setSelectedData(model, newSelectedData)
}, {deep: true})

const {fullData, selectedData} = useLoadableDataRow(rawLoadedDataFile)
selectedData.value = structuredClone(toRaw(defaultSelectedData))

const selectedCollectableData = ref<CollectableEntryConfig>(fromSelectedDataToInitialCollectableData(selectedData.value))
watch(selectedCollectableData, (newSelectedCollectableData) => {
console.log("watching selected collectable data triggered", newSelectedCollectableData)
store.goals[goalName][pathSplit] = newSelectedCollectableData
}, {
deep: true
})


function fromSelectedDataToInitialCollectableData(selectedData: DataRow[]): CollectableEntryConfig {
const resultObj: CollectableEntryConfig = {}
selectedData.forEach(row => {
resultObj[row.code] = {
amountNeeded: 10
}
})
return resultObj
}

const selectAllData = ref<boolean>(false)
watch(selectAllData, newSelectAllData => {
accessOperation.setSelectAllData(model, newSelectAllData)
})

const defaultObject: Object = {amount: 1} as const
selectAllData.value = defaultSelectAllData

function addDataRow(currentlySelectedData: DataRow | undefined, newSelectedData: DataRow) {
function updateSelectedData(currentlySelectedData: string | undefined, newSelectedData: string): void {
if(currentlySelectedData === newSelectedData) {
// The user has clicked the same item that was already selected -> do nothing
return
}
if(currentlySelectedData !== undefined) {
// delete config part, since it's a different variable now
delete store.goals[goalName][pathSplit][currentlySelectedData.code]

store.goals[goalName][pathSplit][newSelectedData.code] = structuredClone(defaultObject)
// replace the old selected mob with the new one in the list
const previousSelectedDataIndex = selectedData.value.indexOf(currentlySelectedData)
//selectedData.value.splice(previousSelectedDataIndex, 1, newSelectedData)
selectedData.value[previousSelectedDataIndex] = newSelectedData
const newlyAdded: CollectableEntryConfig = {
collectableName: newSelectedData
}

const { valid, messages } = validator.isValid(model, (copy) => {
accessOperation.getSelectedData(copy).push(newlyAdded)
})
if(valid) {
pushOrOverrideNewlyAdded(currentlySelectedData, newlyAdded)
}
}

console.log("after replacing: ", selectedData.value)
function updateSelectedDataSpecificAmount(currentlySelectedData: string, newAmount: number): void {
const entries: CollectableEntryConfig[] = accessOperation.getSelectedData(model)
const idx = entries.findIndex((entry: CollectableEntryConfig) => entry.collectableName === currentlySelectedData)
if(idx === -1) {
throw Error(`Failed to retrieve ${currentlySelectedData} from ${entries} when setting the amount.`)
}
const collectableData: CollectableDataConfig | undefined = entries[idx].collectableData
if(collectableData === undefined) {
entries[idx].collectableData = {
amountNeeded: newAmount
}
}
else {
console.log(store.goals)
console.log(goalName)
// store.goals[goalName].mobs = {} //TODO remove
store.goals[goalName][pathSplit][newSelectedData.code] = structuredClone(defaultObject)
selectedData.value.push(newSelectedData)
collectableData.amountNeeded = newAmount
}
// add new config part with default object


}

function deleteDataRow(toDelete: DataRow): void {
const entries: CollectableEntryConfig[] = accessOperation.getSelectedData(model)
const idx = entries.findIndex((entry: CollectableEntryConfig) => entry.collectableName === toDelete.code)
if(idx === -1) {
throw Error(`Failed to retrieve ${toDelete.code} from ${entries} when setting the amount.`)
}
entries.splice(idx, 1)
}

function setCollectAmount(selectedData: DataRow, amount: number) {
//const existingAmount = store.goals[goalName][pathSplit][selectedData.code].amountNeeded
function pushOrOverrideNewlyAdded(currentlySelectedData: string | undefined, newlyAdded: CollectableEntryConfig): void {
const idx = selectedData.value.findIndex((entry: CollectableEntryConfig) => entry.collectableName === currentlySelectedData)
if(idx === -1) {
selectedData.value.push(newlyAdded)
}
else {
selectedData.value[idx] = newlyAdded
}
}

store.goals[goalName][pathSplit][selectedData.code].amountNeeded = amount
function collectableEntryConfig2DataRow(source: DataRow[], entryConfig: CollectableEntryConfig): DataRow {
const found = source.find((dataRow: DataRow) => dataRow.code === entryConfig.collectableName)
if(found === undefined) {
throw Error(`Failed to map ${found} to DataRow, because no matching code can be found. Source is: ${source}`)
}
return found
}

function deleteDataRow(dataRowToDelete: string) {
console.log("to delete", dataRowToDelete)
// delete store.goals[goalName][pathSplit][dataRowToDelete]
delete selectedCollectableData.value[dataRowToDelete]
function collectableEntryConfig2Code(source: CollectableEntryConfig[]): string[] {
return source.map((element: CollectableEntryConfig) => element.collectableName)
}

function transferDataFromPlaceHolderToNewInstance(newSelectedData: DataRow) {
addDataRow(undefined, newSelectedData)
function copyExclude(source: DataRow[], excluding: CollectableEntryConfig[]): DataRow[] {
return source.filter((dataRow: DataRow) => !collectableEntryConfig2Code(excluding).includes(dataRow.code))
}

return { fullData, selectedCollectableData, addDataRow, setCollectAmount, deleteDataRow, transferDataFromPlaceHolderToNewInstance, ...useActivatableGoal(goalName) }
return {
selectedData,
selectAllData,
updateSelectedData,
updateSelectedDataSpecificAmount,
deleteDataRow,
collectableEntryConfig2Code,
collectableEntryConfig2DataRow,
copyExclude
}

}
72 changes: 72 additions & 0 deletions builder_website/src/components/goals/BlockBreakGoal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<template>
<DefaultGoal :goal="goalsView.allgoals.blockbreakGoal">
<div class="flex flex-col space-y-5">
<p class="text-xl">Configuration</p>
<div>
<CollectableGoalEntry v-for="blockMat in collectable.selectedData.value" :key="blockMat.collectableName"
:possible-data="collectable.copyExclude(allBlockMaterialData, collectable.selectedData.value)"
:selected-data="collectable.collectableEntryConfig2DataRow(allBlockMaterialData, blockMat)"
:collectable-prefix="'Block:'"
:amount-prefix="'Amount:'"
@update-selected-data="collectable.updateSelectedData(blockMat.collectableName, $event.code)"
@update-value-for-amount="collectable.updateSelectedDataSpecificAmount(blockMat.collectableName, $event)"
@delete-entry="collectable.deleteDataRow"
:disabled="collectable.selectAllData.value"
/>
<CollectableGoalEntryPlaceholder
:possible-data="collectable.copyExclude(allBlockMaterialData, collectable.selectedData.value)"
:collectable-prefix="'Block:'"
:amount-prefix="'Amount:'"
:place-holder-text="'Select blocks'"
@transfer-data-from-place-holder-to-new-instance="collectable.updateSelectedData(undefined, $event.code)"
:disabled="collectable.selectAllData.value"
/>
</div>
<div class="flex flex-col space-y-4">
<div>
<Checkbox v-model="collectable.selectAllData.value" input-id="selectAllBlockMats" binary/>
<label for="selectAllBlockMats" class="ml-2">Break all blocks once</label>
</div>
</div>
</div>
</DefaultGoal>
</template>

<script setup lang="ts">
import { useConfigStore, useDefaultConfigStore, useJSONSchemaConfigStore, useGoalsViewStore } from '@/main';
import { useValidator } from '../validator';
import DefaultGoal from './DefaultGoal.vue';
import CollectableGoalEntry from './CollectableGoalEntry.vue'
import CollectableGoalEntryPlaceholder from './CollectableGoalEntryPlaceholder.vue';
import type { CollectableEntryConfig, GoalName, GoalsConfig } from '../model/goals';
import { allBlockMaterialData, type DataRow, type MaterialDataRow } from '../loadableDataRow';
import { useCollectableGoal, type AccessOperation } from '../collectableGoal';
import { ref, toRaw, watch } from 'vue';
import Checkbox from 'primevue/checkbox';
import type { BlockBreakGoalConfig } from '../model/blockbreak';
const store = useConfigStore().model
const defaultConfig = useDefaultConfigStore()
const JSONSchemaConfig = useJSONSchemaConfigStore()
const goalsView = useGoalsViewStore()
const validator = useValidator()
// init empty
store.goals!.blockbreakGoal = {}
const access: AccessOperation = {
getSelectedData: (model) => model.goals?.blockbreakGoal?.broken!,
setSelectedData: (model, newSelectedBlockMats) => model.goals!.blockbreakGoal!.broken! = newSelectedBlockMats,
getSelectAllData: (model) => model.goals?.blockbreakGoal?.allBlocks!,
setSelectAllData: (model, newSelectAllBlockMats) => model.goals!.blockbreakGoal!.allBlocks! = newSelectAllBlockMats
}
const collectable = useCollectableGoal(
access,
allBlockMaterialData,
JSONSchemaConfig.BlockBreakGoalConfig.properties.broken.default,
JSONSchemaConfig.BlockBreakGoalConfig.properties.allBlocks.default
)
</script>
44 changes: 24 additions & 20 deletions builder_website/src/components/goals/CollectableGoalEntry.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
<template>
<div class="flex flex-row items-center space-x-4 h-12">
<p>{{ collectablePrefix }}</p>
<Dropdown v-model="newSelectedMob" :options="possibleData" option-label="label"
<Dropdown v-model="newSelectedMob" :options="possibleData" option-label="label" :disabled="disabled"
display="chip" :virtual-scroller-options="{ itemSize: 44 }" filter class="w-full md:w-80">
<template #value="slotProps">
<div v-if="slotProps.value" class="flex justify-start items-center space-x-2">
<img class="w-6" :src="'/rendered_items/' + img + '.png'" :alt="slotProps.value" @error="$event.target.src = 'unknown.png'">
<div>{{ label }}</div>
<img class="w-6" :src="'/rendered_items/' + slotProps.value.img_name + '.png'" :alt="slotProps.value" @error="$event.target.src = 'unknown.png'">
<div>{{ slotProps.value.translation_key }}</div>
</div>
</template>
<template #option="slotProps">
<div class="flex justify-start items-center space-x-2">
<img class="w-6" :src="'/rendered_items/' + img + '.png'" :alt="slotProps.option" @error="$event.target.src = 'unknown.png'">
<div>{{ slotProps.option.label }}</div>
<img class="w-6" :src="'/rendered_items/' + slotProps.option.img_name + '.png'" :alt="slotProps.option" @error="$event.target.src = 'unknown.png'">
<div>{{ slotProps.option.translation_key }}</div>
</div>
</template>
</Dropdown>
<p>{{ amountPrefix }}</p>
<InputNumber v-model="kills" showButtons :min="1" :max="100" :step="1" inputStyle="width:32px" />
<Button v-if="selectedData" label="Delete" @click="$emit('deleteEntry', selectedData)" />
<InputNumber v-model="kills" showButtons :min="1" :max="100" :step="1" :disabled="disabled" inputStyle="width:32px" />
<Button v-if="selectedData" label="Delete" @click="$emit('deleteEntry', selectedData)" :disabled="disabled" />
</div>
</template>

Expand All @@ -28,31 +28,35 @@ import InputNumber from 'primevue/inputnumber';
import Button from 'primevue/button';
import { ref, defineComponent, toRef, watch, computed } from 'vue'
import { useConfigStore } from '@/main';
import { type DataRow } from '../loadableDataRow';
const props = defineProps({
selectedData: String,
label: String,
img: String,
possibleData: [],
collectablePrefix: String,
amountPrefix: String
})
const props = defineProps<{
selectedData: DataRow,
possibleData: DataRow[],
collectablePrefix: string,
amountPrefix: string,
disabled: boolean
}>()
console.log(props.selectedData)
const newSelectedMob = computed({
get: () => props.selectedData,
set: (newValue) => emit('update:selectedData', newValue)
set: (newValue) => emit('updateSelectedData', newValue)
})
const store = useConfigStore().model
const kills = ref(1)
watch(kills, (newKills) => {
emit('update:valueForAmount', newKills)
emit('updateValueForAmount', newKills)
//store.goals.MobGoal.settings.mobs[props.selectedData.code].amountNeeded = newKills
})
const emit = defineEmits(["deleteEntry", "update:selectedData", "update:valueForAmount"])
// const emit = defineEmits(["deleteEntry", "update:selectedData", "update:valueForAmount"])
const emit = defineEmits<{
updateSelectedData: [newSelectedData: DataRow]
updateValueForAmount: [newAmount: number]
deleteEntry: [dataRowToDelete: DataRow]
}>()
defineComponent({
Dropdown,
InputNumber,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
<template>
<div class="flex flex-row items-center space-x-4 h-12">
<p>{{ collectablePrefix }}</p>
<Dropdown v-model="selectedMob" :options="possibleMobs" option-label="label" :placeholder="placeHolderText"
<Dropdown v-model="selectedData" :options="possibleData" option-label="label" :placeholder="placeHolderText" :disabled="disabled"
display="chip" :virtual-scroller-options="{ itemSize: 44 }" filter class="w-full md:w-80"
@update:modelValue="$emit('transferDataFromPlaceHolderToNewInstance', selectedMob); selectedMob=null">
@update:modelValue="$emit('transferDataFromPlaceHolderToNewInstance', selectedData!); selectedData=undefined">
<template #value="slotProps">
<div v-if="slotProps.value" class="flex justify-start items-center space-x-2">
<img class="w-6" :src="'/rendered_items/' + slotProps.value.image + '.png'" :alt="slotProps.value" @error="$event.target.src = 'unknown.png'">
<div>{{ slotProps.value.label }}</div>
<img class="w-6" :src="'/rendered_items/' + slotProps.value.img_name + '.png'" :alt="slotProps.value" @error="$event.target!.src = 'unknown.png'">
<div>{{ slotProps.value.translation_key }}</div>
</div>
</template>
<template #option="slotProps">
<div class="flex justify-start items-center space-x-2">
<img class="w-6" :src="'/rendered_items/' + slotProps.option.image + '.png'" :alt="slotProps.option" @error="$event.target.src = 'unknown.png'">
<div>{{ slotProps.option.label }}</div>
<img class="w-6" :src="'/rendered_items/' + slotProps.option.img_name + '.png'" :alt="slotProps.option" @error="$event.target!.src = 'unknown.png'">
<div>{{ slotProps.option.translation_key }}</div>
</div>
</template>
</Dropdown>
<p>{{ amountPrefix }}</p>
<InputNumber v-model="kills" showButtons :min="1" :max="100" :step="1" :disabled="!selectedMob" inputStyle="width:32px" />
<InputNumber v-model="kills" showButtons :min="1" :max="100" :step="1" :disabled="!selectedData" inputStyle="width:32px" />
</div>
</template>

Expand All @@ -28,11 +28,21 @@ import InputNumber from 'primevue/inputnumber';
import Button from 'primevue/button';
import { ref, defineComponent, toRef, watch } from 'vue'
import { useConfigStore } from '@/main';
import type { DataRow } from '../loadableDataRow';
const props = defineProps(['possibleMobs', 'collectablePrefix', 'amountPrefix', 'placeHolderText'])
const selectedMob = ref()
//const props = defineProps(['possibleMobs', 'collectablePrefix', 'amountPrefix', 'placeHolderText'])
const props = defineProps<{
possibleData: DataRow[]
collectablePrefix: string
amountPrefix: string
placeHolderText: string
disabled: boolean
}>()
const selectedData = ref<DataRow>()
const kills = 0
defineEmits(["transferDataFromPlaceHolderToNewInstance"])
// defineEmits(["transferDataFromPlaceHolderToNewInstance"])
const emits = defineEmits<{
transferDataFromPlaceHolderToNewInstance: [newSelectedData: DataRow]
}>()
</script>
Loading

0 comments on commit 20f2195

Please sign in to comment.