More item types
Some checks failed
test / run-tests-client (push) Successful in 43s
test / run-tests-backend (push) Failing after 14s

This commit is contained in:
BinarySandia04 2024-10-18 15:11:08 +02:00
parent dedbde13db
commit 73d10a7846
16 changed files with 301 additions and 110 deletions

View File

@ -7,7 +7,6 @@ const BookSchema = new Schema({
description: { type: String },
system: {type: String, required: true},
image: { type: String },
contents: [ {type: mongoose.Types.ObjectId, ref: "Concept"} ],
});
module.exports = mongoose.model('Book', BookSchema);

View File

@ -307,9 +307,50 @@ span.artifact {
}
.form-element label {
flex-grow: 1;
flex-grow: 0;
margin-right: 6px;
margin-left: 6px;
}
.form-element.centered {
justify-content: center;
}
.grow {
flex-grow: 1;
}
.subsection.border:first-child {
border-left: none;
}
.subsection.border {
border-left: 1px solid var(--color-border);
}
.subsection {
margin-left: 5px;
margin-right: 5px;
height: 32px;
display: flex;
align-items: left;
justify-content: left;
}
.subsection.left {
align-items: left;
justify-content: left;
}
.subsection.right {
align-items: right;
justify-content: right;
}
.subsection.center {
align-items: center;
justify-content: center;
}

View File

@ -71,8 +71,8 @@ function PopulateContext(val){
animate(contextMenuElement, {
opacity: [0, 1],
translateY: [20, -2]
}, {delay: (elementNum / 2) * 0.1, duration: 0.25}).finished.then(() => {
translateY: [-20, -2]
}, {duration: 0.15}).finished.then(() => {
});
elementNum++;

View File

@ -0,0 +1,31 @@
function GetKey(from, key){
let k = key.split('.');
let obj = from;
for(let i = 0; i < k.length; i++){
if(typeof obj !== 'object'){
// We found a literal before ending!
return;
}
if(Object.keys(obj).includes(k[i])){
obj = obj[k[i]];
} else return;
}
return obj;
}
function SetKey(from, key, value){
let k = key.split('.');
let obj = from;
for(let i = 0; i < k.length - 1; i++){
if(!Object.keys(obj).includes(k[i])){
obj[k[i]] = {};
}
obj = obj[k[i]];
}
obj[k[k.length - 1]] = value;
}
export {
GetKey,
SetKey
}

View File

@ -2,6 +2,7 @@
import { onMounted, ref, watch } from 'vue';
import { AddContextMenu } from '@/services/ContextMenu';
import { AddTooltip } from '@/services/Tooltip';
import { GetKey } from '@/services/Utils';
import { marked } from "marked";
const props = defineProps(['element', 'context', 'tooltip', 'icon']);
@ -14,7 +15,8 @@ const icon = ref("icons/game-icons/ffffff/lorc/crossed-swords.svg")
async function updateElement(){
element.value = props.element;
// Do whatever
let desc = element.value.info.description;
let desc = undefined;
GetKey(element.value, "info.description", (val) => desc = val);
desc = desc ? marked.parse(desc) : '';
if(props.icon) icon.value = await props.icon(element.value);

View File

@ -11,6 +11,7 @@ const selected = ref(initialSelect);
onMounted(() => {
let context = [];
if(props.selected == undefined) selected.value = "undefined";
watch(() => props.selected, () => {
selected.value = props.selected;
});
@ -21,7 +22,7 @@ onMounted(() => {
action: () => {
HideContextMenu();
selected.value = name;
selectCallback(name);
if(selectCallback) selectCallback(name);
}
});
});
@ -34,12 +35,27 @@ onMounted(() => {
<template>
<div class="dropdown" ref="dropdown">
<span>{{ selected }}</span>
<img class="icon" src="/icons/iconoir/regular/nav-arrow-down.svg" draggable="false" ref="closeButton">
</div>
</template>
<style scoped lang="scss">
.dropdown {
background-color: #181818;
padding: 5px;
flex-grow: 1;
display: flex;
background-color: var(--color-background-softer);
border: none;
padding: 4px 8px 4px 8px;
margin: 0 6px 0px 6px;
border-radius: 6px;
color: var(--color-text);
transition: 300ms background-color;
border: solid 1px var(--color-border);
.icon {
margin-left: auto;
justify-content: right;
}
}
</style>

View File

@ -0,0 +1,8 @@
<script setup>
</script>
<template>
<div class="form-element">
<slot></slot>
</div>
</template>

View File

@ -23,7 +23,9 @@ defineExpose({
<style lang="scss" scoped>
.number-input {
max-width: 70px;
flex-grow: 1;
flex-shrink: 1;
text-align: center;
width: 100px;
}
</style>

View File

@ -71,6 +71,7 @@ defineExpose({
<style scoped lang="scss">
.tags-container {
display: flex;
flex-grow: 1;
flex-wrap: wrap;
padding-left: 4px;
padding-right: 4px;

View File

@ -110,13 +110,5 @@
"systems": {
"title": "Select a game system",
"not-selected": "No game system selected"
},
"database": {
"title": "Database",
"tabs": {
"items": "Items",
"spells": "Spells",
"features": "Features"
}
}
}

View File

@ -16,18 +16,13 @@ function Main(api){
book: { type: "ObjectId", ref: "Book"}
});
dndModule.router.get('/testing', (req, res) => {
/*
let item = itemModel.create({
name: "Test item!",
type: "The test item"
})
*/
res.json({
status: "ok"
})
})
let entityModel = Api.createModel('entity', {
});
let characterModel = Api.createModel('character', {
});
dndModule.router.get('/item/list', (req, res) => {
const campaign = req.query.campaign;
@ -50,7 +45,6 @@ function Main(api){
res.json({status: "ok", item});
});
});
dndModule.router.get('/item/get', (req, res) => {
const campaign = req.query.campaign;
let id = req.query.id;
@ -59,7 +53,6 @@ function Main(api){
res.json({status: "ok", concept});
});
})
dndModule.router.put('/item/update', (req, res) => {
const campaign = req.query.campaign;
let id = req.query.id;

View File

@ -68,7 +68,7 @@ function ConfigureBookmarks(){
<img class="icon bookmark-icon" draggable="false" src="/icons/game-icons/ffffff/lorc/book-cover.svg">
</div>
<div class="bookmark">
<img class="icon bookmark-icon" draggable="false" src="/icons/game-icons/ffffff/lorc/power-lightning.svg">
<img class="icon bookmark-icon" draggable="false" src="/icons/game-icons/ffffff/lorc/scroll-unfurled.svg">
</div>
<div class="bookmark">
<img class="icon bookmark-icon" draggable="false" src="/icons/game-icons/ffffff/lorc/feather.svg">

View File

@ -16,7 +16,7 @@ const PluginData = Global('dnd-5e').Data;
onMounted(() => {
SetupHandle(id, handle);
SetSize(id, {width: 250, height: 320});
SetSize(id, {width: 250, height: 410});
ResetPosition(id, "center");
});
@ -73,6 +73,18 @@ function ConfirmSelection(){
<span>Tool</span>
<input type="radio" name="selector" value="Tool">
</div>
<div class="radio-item">
<img class="icon" src="/icons/game-icons/000000/lorc/scroll-unfurled.svg">
<span>Spell</span>
<input type="radio" name="selector" value="Spell">
</div>
<div class="radio-item">
<img class="icon" src="/icons/game-icons/000000/delapouite/round-star.svg">
<span>Feature</span>
<input type="radio" name="selector" value="Feature">
</div>
</div>
<button class="btn-primary sound-click submit" v-on:click.prevent="ConfirmSelection">

View File

@ -1,4 +1,6 @@
<script setup>
import { marked } from "marked";
import WindowHandle from '@/views/partials/WindowHandle.vue';
import { onMounted, ref, shallowRef, watch } from 'vue';
@ -7,6 +9,7 @@ import ConceptList from '@/views/partials/ConceptList.vue';
import Tabs from '@/views/partials/Tabs.vue';
import FixedBottomButtons from '@/views/partials/FixedBottomButtons.vue';
import { Global } from '@/services/PluginGlobals';
import { GetKey } from '@/services/Utils.js';
import { FetchConcepts, GetConcepts } from './../data.js'
@ -19,17 +22,34 @@ const Api = Global('dnd-5e').Api;
const PluginData = Global('dnd-5e').Data;
let id = data.id;
const elements = shallowRef([]);
const weapons = shallowRef([]);
const equipment = shallowRef([]);
const consumables = shallowRef([]);
const containers = shallowRef([]);
const tools = shallowRef([]);
const spells = shallowRef([]);
const features = shallowRef([]);
onMounted(() => {
SetupHandle(id, handle);
SetSize(id, {width: 700, height: 800});
SetSize(id, {width: 800, height: 800});
ResetPosition(id, "center");
SetResizable(id, true);
SetMinSize(id, {width: 350, height: 300});
SetMinSize(id, {width: 800, height: 300});
watch(GetConcepts, () => {
elements.value = GetConcepts();
let elements = GetConcepts();
weapons.value = elements.filter((e) => e.type == "Weapon");
equipment.value = elements.filter((e) => e.type == "Equipment");
consumables.value = elements.filter((e) => e.type == "Consumable");
containers.value = elements.filter((e) => e.type == "Container");
tools.value = elements.filter((e) => e.type == "Tool");
spells.value = elements.filter((e) => e.type == "Spell");
features.value = elements.filter((e) => e.type == "Feature");
console.log(elements);
console.log(elements);
});
FetchConcepts();
@ -55,15 +75,18 @@ function ElementContext(element){
}
function ElementTooltip(element){
let descHtml = GetKey(element, 'info.description');
if(descHtml) descHtml = marked.parse(descHtml);
else descHtml = '';
return `<div class='document item'>
<h2>${element.name}</h2>
<img src='${element.info.icon}'></img>
<div class='document'>${element.info.description ?? ''}</div>
<img src='${GetKey(element, "info.icon")}'></img>
<div class='document'>${descHtml}</div>
</div>`;
}
function ElementIcon(element){
return element.info ? element.info.icon : 'icons/game-icons/ffffff/lorc/crossed-swords.svg'
return GetKey(element, "info.icon") ? GetKey(element, "info.icon") : 'icons/game-icons/ffffff/lorc/crossed-swords.svg'
}
</script>
@ -74,13 +97,71 @@ function ElementIcon(element){
<div class="main-container">
<Tabs :rows="[
{id: 'items', value: 'database.tabs.items'},
{id: 'spells', value: 'database.tabs.spells'},
{id: 'features', value: 'database.tabs.features'}
{id: 'weapons', value: 'plugins.dnd-5e.database.tabs.weapons'},
{id: 'equipment', value: 'plugins.dnd-5e.database.tabs.equipment'},
{id: 'consumables', value: 'plugins.dnd-5e.database.tabs.consumables'},
{id: 'containers', value: 'plugins.dnd-5e.database.tabs.containers'},
{id: 'tools', value: 'plugins.dnd-5e.database.tabs.tools'},
{id: 'spells', value: 'plugins.dnd-5e.database.tabs.spells'},
{id: 'features', value: 'plugins.dnd-5e.database.tabs.features'},
]">
<template #items>
<template #weapons>
<ConceptList
:elements="elements"
:elements="weapons"
:open="OpenConcept"
:context="ElementContext"
:tooltip="ElementTooltip"
:icon="ElementIcon"
></ConceptList>
</template>
<template #equipment>
<ConceptList
:elements="equipment"
:open="OpenConcept"
:context="ElementContext"
:tooltip="ElementTooltip"
:icon="ElementIcon"
></ConceptList>
</template>
<template #consumables>
<ConceptList
:elements="consumables"
:open="OpenConcept"
:context="ElementContext"
:tooltip="ElementTooltip"
:icon="ElementIcon"
></ConceptList>
</template>
<template #containers>
<ConceptList
:elements="containers"
:open="OpenConcept"
:context="ElementContext"
:tooltip="ElementTooltip"
:icon="ElementIcon"
></ConceptList>
</template>
<template #tools>
<ConceptList
:elements="tools"
:open="OpenConcept"
:context="ElementContext"
:tooltip="ElementTooltip"
:icon="ElementIcon"
></ConceptList>
</template>
<template #spells>
<ConceptList
:elements="spells"
:open="OpenConcept"
:context="ElementContext"
:tooltip="ElementTooltip"
:icon="ElementIcon"
></ConceptList>
</template>
<template #features>
<ConceptList
:elements="features"
:open="OpenConcept"
:context="ElementContext"
:tooltip="ElementTooltip"

View File

@ -10,9 +10,13 @@ import { AddContextMenu, HideContextMenu, ShowContextMenu } from '@/services/Con
import Tabs from '@/views/partials/Tabs.vue';
import MarkdownEditor from '@/views/partials/MarkdownEditor.vue';
import Tags from '@/views/partials/Tags.vue';
import NumberInput from '@/views/partials/NumberInput.vue';
import Input from '@/views/partials/Input.vue';
import { GetKey, SetKey } from '@/services/Utils.js';
import { Global } from '@/services/PluginGlobals';
import Dropdown from '@/views/partials/Dropdown.vue';
import FormElement from '@/views/partials/FormElement.vue';
const props = defineProps(['data']);
const data = props.data;
const api = Global('dnd-5e').Api;
@ -31,6 +35,7 @@ const properties = ref(null);
const quantity = ref(null);
const weight = ref(null);
const price = ref(null);
const item_type_name = ref("");
function GenRarities(){
let rarities = [];
@ -81,7 +86,7 @@ function Upload(){
}
function SetParam(param, value){
concept.value.info[param] = value;
SetKey(concept.value, `info.${param}`, value);
Upload();
}
@ -100,19 +105,17 @@ function PropertiesChanged(properties){
function InitValues(){
let rarities = GenRarities();
let weapon_types = GenTypes(["", "Melee", "Ranged", "Martial Melee", "Martial Ranged", "Natural", "Improvised", "Siege Weapon"]);
if(!concept.value.data) concept.value.data = {};
if(!concept.value.info) concept.value.info = {};
if(concept.value.info.icon) icon_selector.value.icon = concept.value.info.icon;
if(concept.value.info.rarity) rarity.value.innerHTML = `<span class='important ${concept.value.info.rarity.replace(/\s+/g, '-').toLowerCase()}'>${concept.value.info.rarity}</span>`;
if(concept.value.info.weapon_type) weaponType.value.innerHTML = `<span class='important'>${concept.value.info.weapon_type}</span>`;
if(concept.value.info.description) description.value.text = concept.value.info.description;
if(concept.value.info.properties) properties.value.selected = concept.value.info.properties;
if(concept.value.info.quantity) quantity.value.Set(concept.value.info.quantity);
if(concept.value.info.weight) weight.value.Set(concept.value.info.weight);
if(concept.value.info.price) price.value.Set(concept.value.info.price);
icon_selector.value.icon = GetKey(concept.value, "info.icon");
rarity.value.innerHTML = `<span class='important ${GetKey(concept.value, "info.rarity") ? GetKey(concept.value, "info.rarity").replace(/\s+/g, '-').toLowerCase() : ""}'>${GetKey(concept.value, "info.rarity")}</span>`;
weaponType.value.innerHTML = `<span class='important'>${GetKey(concept.value, "info.weapon_type")}</span>`;
description.value.text = GetKey(concept.value, "info.description");
properties.value.selected = GetKey(concept.value, "info.properties");
quantity.value.Set(GetKey(concept.value, "info.quantity"));
weight.value.Set(GetKey(concept.value, "info.weight"));
price.value.Set(GetKey(concept.value, "info.price"));
item_type_name.value = GetKey(concept.value, "type");
item_name.value.innerHTML = GetKey(concept.value, "name");
quantity.value.OnUpdate((val) => SetParam('quantity', val));
weight.value.OnUpdate((val) => SetParam('weight', val));
@ -135,34 +138,32 @@ function InitValues(){
}
onMounted(() => {
SetupHandle(id, handle);
SetSize(id, {width: 600, height: 700});
SetResizable(id, true);
SetMinSize(id, {width: 400, height: 300});
ResetPosition(id, "center");
item_type.value = data.item_type;
if(data.item_create){
dndModule.router.post('/item/create', {}, {
data: {
type: data.item_type,
name: "New " + data.item_type
},
}).then(response => {
concept.value = response.data.concept;
InitValues();
}).catch(err => console.log(err));
} else {
// Get concept
GetConcept(data.item_id).then(response => {
concept.value = response.data.concept;
InitValues();
}).catch(err => console.log(err));
}
});
item_type.value = data.item_type;
if(data.item_create){
dndModule.router.post('/item/create', {}, {
data: {
type: data.item_type,
name: "New " + data.item_type
},
}).then(response => {
concept.value = response.data.concept;
InitValues();
}).catch(err => console.log(err));
} else {
// Get concept
GetConcept(data.item_id).then(response => {
concept.value = response.data.concept;
InitValues();
}).catch(err => console.log(err));
}
</script>
@ -174,10 +175,13 @@ onMounted(() => {
<div class="item-header">
<IconSelector :window="id" ref="icon_selector" :done="IconSelected"></IconSelector>
<div class="header-info">
<h1 contenteditable="true" spellcheck="false" ref="item_name">{{ concept.name }}</h1>
<div class="row">
<div class="grow subsection" ref="weaponType"></div>
<div class="grow subsection" ref="rarity"></div>
<h1 class="grow subsection left" contenteditable="true" spellcheck="false" ref="item_name"></h1>
<h1 class="subsection right">{{ item_type_name }}</h1>
</div>
<div class="row">
<div class="grow subsection center border" ref="weaponType"></div>
<div class="grow subsection center border" ref="rarity"></div>
</div>
</div>
</div>
@ -189,18 +193,18 @@ onMounted(() => {
<div class="description-container">
<div class="description-sidebar">
<div class="form-container">
<div class="form-element">
<FormElement>
<label>{{$t('general.quantity')}}</label>
<NumberInput ref="quantity"></NumberInput>
</div>
<div class="form-element">
<Input ref="quantity"></Input>
</FormElement>
<FormElement>
<label>{{$t('general.weight')}}</label>
<NumberInput ref="weight"></NumberInput>
</div>
<div class="form-element">
<Input ref="weight"></Input>
</FormElement>
<FormElement>
<label>{{$t('general.price')}}</label>
<NumberInput ref="price"></NumberInput>
</div>
<Input ref="price"></Input>
</FormElement>
</div>
</div>
<div class="description">
@ -210,8 +214,21 @@ onMounted(() => {
</template>
<template #details>
<h2 class="section">Properties</h2>
<Tags ref="properties" :items="['Amunnition','Finesse','Heavy','Light','Loading','Range','Reach','Special','Thrown','Two-Handed','Versatile']" :done="PropertiesChanged"></Tags>
<FormElement>
<label>Properties</label>
<Tags ref="properties" :items="['Amunnition','Finesse','Heavy','Light','Loading','Range','Reach','Special','Thrown','Two-Handed','Versatile']" :done="PropertiesChanged"></Tags>
</FormElement>
<h2 class="section">Usage</h2>
<FormElement>
<label>Range</label>
<Input></Input><label>/</label><Input></Input><Dropdown :options="['ft', 'm']" :selected="'ft'"></Dropdown>
</FormElement>
<h2 class="section">Damage</h2>
<FormElement>
<label>Damage</label>
<Dropdown :options="['None','Acid','Bludgeoning','Cold','Fire','Force','Lightning','Necrotic','Piercing','Poison','Psychic','Radiant','Slashing','Thunder','Healing','Healing (Temp)']"></Dropdown>
<Input></Input>
</FormElement>
</template>
</Tabs>
</div>
@ -245,7 +262,7 @@ h2.section {
width: 100%;
.description-sidebar {
min-width: 200px;
max-width: 200px;
}
.description {
@ -285,10 +302,6 @@ h2.section {
width: 100%;
}
.grow {
flex-grow: 1;
}
.window-wrapper {
display: flex;
align-items: center;
@ -296,15 +309,4 @@ h2.section {
user-select: none;
}
.subsection {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
&:first-child {
border-right: 1px solid var(--color-border);
}
}
</style>

View File

@ -1,3 +1,14 @@
{
"test": "Test"
"database": {
"title": "Database",
"tabs": {
"weapons": "Weapons",
"equipment": "Equipment",
"consumables": "Consumables",
"containers": "Containers",
"tools": "Tools",
"spells": "Spells",
"features": "Features"
}
}
}