add (read-only) Compass, add KSX_TRACK_MOODS type

feat/vaults
Tomáš Mládek 2021-06-26 17:55:26 +02:00
parent d8ee5aa82a
commit 34c1b68dd1
6 changed files with 344 additions and 41 deletions

View File

@ -0,0 +1,87 @@
<template>
<div class="attribute-view">
<component
v-for="component in components"
:key="component.id"
:is="component.name"
v-bind="component.props"
:attributes="attributes"
:insertable="insertable"
@edit="processChange"
/>
</div>
</template>
<script lang="ts">
import Compass from "@/components/widgets/Compass.vue";
import Table from "@/components/widgets/Table.vue";
import { UpType } from "@/lib/types";
import { AttributeChange, IEntry } from "@/types/base";
import { ComponentOptions, defineComponent, PropType } from "vue";
export default defineComponent({
name: "AttributeView",
components: {
Table,
Compass,
},
emits: ["edit"],
props: {
address: {
type: String,
required: true,
},
type: {
type: Object as PropType<UpType>,
},
attributes: {
type: Array as PropType<[string, IEntry][]>,
required: true,
},
insertable: {
type: Boolean,
default: false,
},
},
computed: {
components(): ComponentOptions[] {
return (
this.type?.widgetInfo || [
{
name: "Table",
},
]
);
},
},
methods: {
async processChange(change: AttributeChange) {
switch (change.type) {
case "create":
await fetch(`/api/obj`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
entity: this.address,
attribute: change.attribute,
value: {
t: "Value",
c: change.value,
},
}),
});
break;
case "delete":
await fetch(`/api/obj/${change.id}`, { method: "DELETE" });
break;
default:
console.error(`Unimplemented: ${change}`);
}
this.$emit("edit");
},
},
});
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,202 @@
<template>
<div class="compass-widget">
<div class="compass-row-helper">
<div class="compass-label compass-label-left">{{ leftLabel }}</div>
<div class="compass-container">
<div class="compass-label compass-label-top">{{ topLabel }}</div>
<div class="compass">
<div class="compass-x-axis"></div>
<div class="compass-y-axis"></div>
<div
class="compass-marker compass-select-marker"
:style="{
bottom: markerBottom,
left: markerLeft,
}"
></div>
<div class="context-markers"></div>
</div>
<div class="compass-label compass-label-bottom">{{ bottomLabel }}</div>
</div>
<div class="compass-label compass-label-right">{{ rightLabel }}</div>
</div>
<div class="compass-fields">
<div class="compass-field">
<label for="compass_field_x_{{ widget.name }}">
{{ xLabel }}
</label>
<input
type="number"
v-model.number="xVal"
:min="-xRange"
:max="xRange"
/>
</div>
<div class="compass-field">
<label for="compass_field_y_{{ widget.name }}">
{{ yLabel }}
</label>
<input
type="number"
v-model.number="yVal"
:min="-yRange"
:max="yRange"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { asDict } from "@/lib/entity";
import { IEntry } from "@/types/base";
import { defineComponent, PropType, ref } from "vue";
export default defineComponent({
name: "Compass",
props: {
attributes: {
type: Array as PropType<[string, IEntry][]>,
required: true,
},
xAttrName: {
type: String,
required: true,
},
yAttrName: {
type: String,
required: true,
},
xLabel: {
type: String,
default: "X",
},
yLabel: {
type: String,
default: "Y",
},
xRange: {
type: Number,
default: 100,
},
yRange: {
type: Number,
default: 100,
},
},
computed: {
topLabel(): string | undefined {
return this.yLabel.includes("//")
? this.yLabel.split("//")[1]
: undefined;
},
bottomLabel(): string | undefined {
return this.yLabel.includes("//")
? this.yLabel.split("//")[0]
: undefined;
},
leftLabel(): string | undefined {
return this.xLabel.includes("//")
? this.xLabel.split("//")[0]
: undefined;
},
rightLabel(): string | undefined {
return this.xLabel.includes("//")
? this.xLabel.split("//")[1]
: undefined;
},
markerBottom(): string {
return `${((this.yVal + this.yRange) / (this.yRange * 2)) * 100}%`;
},
markerLeft(): string {
console.log(this.xVal);
return `${((this.xVal + this.xRange) / (this.xRange * 2)) * 100}%`;
},
},
setup(props) {
const attrs = asDict(props.attributes);
const xVal = ref(parseInt(attrs[props.xAttrName]) || -1);
const yVal = ref(parseInt(attrs[props.yAttrName]) || -1);
return {
xVal,
yVal,
};
},
});
</script>
<style lang="scss" scoped>
.compass-container {
width: 100%;
}
.compass {
position: relative;
width: 100%;
padding-top: 100%; /* 1:1 Aspect Ratio */
}
.compass-row-helper {
display: flex;
align-items: center;
}
.compass-label {
margin: 0.5em;
text-align: center;
}
.compass-marker {
position: absolute;
width: 1.5%;
height: 1.5%;
background: red;
}
.context-markers div {
opacity: 25%;
}
.compass-x-axis {
position: absolute;
top: 50%;
left: 0;
height: 0;
width: 100%;
border: 1px dashed gray;
}
.compass-y-axis {
position: absolute;
top: 0;
left: 50%;
width: 0;
height: 100%;
border: 1px dashed gray;
}
.compass-fields {
display: flex;
justify-content: center;
margin: 1rem 0;
.compass-field {
margin: 0 1em;
text-align: center;
label {
display: inline-block;
margin: 0 0.5em;
}
input {
width: 4em;
font-family: monospace;
text-align: left;
}
}
}
</style>

View File

@ -58,7 +58,7 @@ export default defineComponent({
Address,
Marquee,
},
emits: ["change"],
emits: ["edit"],
props: {
attributes: {
type: Array as PropType<[string, IEntry][]>,
@ -81,7 +81,7 @@ export default defineComponent({
},
methods: {
async addEntry() {
this.$emit("change", {
this.$emit("edit", {
type: "create",
attribute: this.newEntryAttribute,
value: this.newEntryValue,
@ -92,7 +92,7 @@ export default defineComponent({
},
async removeEntry(id: string) {
if (confirm("Are you sure you want to remove the attribute?")) {
this.$emit("change", { type: "delete", id } as AttributeChange);
this.$emit("edit", { type: "delete", id } as AttributeChange);
}
},
},

View File

@ -114,3 +114,11 @@ export function identify(attributes: ComputedRef<[string, IEntry][]>): ComputedR
}).flat();
});
}
export function asDict(attributes: [string, IEntry][]): { [key: string]: string } {
const result = {} as { [key: string]: string };
attributes.map(([_, attribute]) => attribute).forEach((attribute) => {
result[attribute.attribute] = attribute.value.c;
});
return result;
}

View File

@ -1,15 +1,45 @@
import { ComponentOptions } from "vue";
export class UpType {
name: string | null = null;
attributes: string[] = [];
public getIcon(): string | undefined {
public get icon(): string | undefined {
return this.name ? TYPE_ICONS[this.name] : undefined;
}
public get widgetInfo(): ComponentOptions[] | undefined {
return this.name ? TYPE_WIDGETS[this.name] : undefined;
}
}
const TYPE_ICONS: { [key: string]: string } = {
"BLOB": "box",
"FS_FILE": "file-earmark",
"FS_DIR": "folder"
}
const TYPE_WIDGETS: { [key: string]: ComponentOptions[] } = {
"KSX_TRACK_MOODS": [
{
name: "Compass",
id: "compass_tint_energy",
props: {
xAttrName: "KSX_TINT",
yAttrName: "KSX_ENERGY",
xLabel: "Lightsoft // Heavydark",
yLabel: "Chill // Extreme",
}
},
{
name: "Compass",
id: "compass_seriousness_materials",
props: {
xAttrName: "KSX_SERIOUSNESS",
yAttrName: "KSX_MATERIALS",
xLabel: "Dionysia // Apollonia",
yLabel: "Natural // Reinforced",
}
}
]
}

View File

@ -14,23 +14,26 @@
:key="typeAddr"
>
<h3>
<sl-icon
v-if="types[typeAddr].getIcon()"
:name="types[typeAddr].getIcon()"
/>
<sl-icon v-if="types[typeAddr].icon" :name="types[typeAddr].icon" />
{{ types[typeAddr]?.name || "???" }}
</h3>
<Table :attributes="attributes" @change="processChange" />
<AttributeView
:address="address"
:type="types[typeAddr]"
:attributes="attributes"
@edit="mutate()"
/>
</section>
<section class="untyped-attribute-list">
<h3>
<sl-icon name="question-diamond" />
Other attributes
</h3>
<Table
<AttributeView
insertable
:address="address"
:attributes="untypedAttributes"
@change="processChange"
@change="mutate()"
/>
</section>
<template v-if="backlinks.length">
@ -63,12 +66,12 @@
<script lang="ts">
import Address from "@/components/Address.vue";
import AttributeView from "@/components/AttributeView.vue";
import BlobPreview from "@/components/BlobPreview.vue";
import Marquee from "@/components/Marquee.vue";
import Table from "@/components/widgets/Table.vue";
import { query, useEntity } from "@/lib/entity";
import { UpType } from "@/lib/types";
import { AttributeChange, IEntry } from "@/types/base";
import { IEntry } from "@/types/base";
import { computed, defineComponent, reactive, watch } from "vue";
export default defineComponent({
@ -77,7 +80,7 @@ export default defineComponent({
Address,
BlobPreview,
Marquee,
Table,
AttributeView,
},
props: {
address: {
@ -85,33 +88,6 @@ export default defineComponent({
required: true,
},
},
methods: {
async processChange(change: AttributeChange) {
switch (change.type) {
case "create":
await fetch(`/api/obj`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
entity: this.address,
attribute: change.attribute,
value: {
t: "Value",
c: change.value,
},
}),
});
break;
case "delete":
await fetch(`/api/obj/${change.id}`, { method: "DELETE" });
await this.mutate();
break;
default:
console.error(`Unimplemented: ${change}`);
}
this.mutate();
},
},
setup(props) {
const { error, mutate, attributes, backlinks } = useEntity(
() => props.address