add (read-only) Compass, add KSX_TRACK_MOODS type
parent
d8ee5aa82a
commit
34c1b68dd1
|
@ -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>
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue