upend/ui/src/views/Inspect.vue

254 lines
5.9 KiB
Vue

<template>
<div class="inspect">
<h2>
<Address
:address="address"
:is-file="backlinks.some(([_, e]) => e.attribute === 'FILE_IS')"
/>
</h2>
<blob-preview :address="address" />
<div v-if="!error">
<section
class="typed-attribute-list"
v-for="(attributes, typeAddr) in typedAttributes"
:key="typeAddr"
>
<h3>
<up-link :to="{ entity: typeAddr }">
<sl-icon v-if="types[typeAddr].icon" :name="types[typeAddr].icon" />
{{ types[typeAddr]?.name || "???" }}
</up-link>
</h3>
<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>
<AttributeView
insertable
:address="address"
:attributes="untypedAttributes"
@change="mutate()"
/>
</section>
<template v-if="backlinks.length">
<section class="backlinks">
<h3>Referred to ({{ backlinks.length }})</h3>
<table>
<tr>
<th>Entities</th>
<th>Attribute names</th>
</tr>
<tr v-for="[id, entry] in backlinks" :key="id">
<td>
<Address link :address="entry.entity" />
</td>
<td>
<Marquee>
{{ entry.attribute }}
</Marquee>
</td>
</tr>
</table>
</section>
</template>
</div>
<div v-else class="error">
{{ error }}
</div>
</div>
</template>
<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 UpLink from "@/components/UpLink.vue";
import { query, useEntity } from "@/lib/entity";
import { UpType } from "@/lib/types";
import { IEntry } from "@/types/base";
import { computed, defineComponent, reactive, watch } from "vue";
export default defineComponent({
name: "Inspect",
components: {
Address,
BlobPreview,
Marquee,
AttributeView,
UpLink,
},
props: {
address: {
type: String,
required: true,
},
},
setup(props) {
const { error, mutate, attributes, backlinks } = useEntity(
() => props.address
);
const allTypeAddresses = computed(() => {
return attributes.value
.map(([_, attr]) => attr)
.filter((attr) => attr.attribute == "IS")
.map((attr) => attr.value.c);
});
const { result: allTypeEntries } = query(
() =>
`(matches (in ${allTypeAddresses.value
.map((addr) => `"${addr}"`)
.join(" ")}) ? ?)`,
() => allTypeAddresses.value.length > 0
);
const allTypes = computed(() => {
const result = {} as { [key: string]: UpType };
allTypeEntries.value.forEach(([_, entry]) => {
if (result[entry.entity] === undefined) {
result[entry.entity] = new UpType();
}
switch (entry.attribute) {
case "TYPE":
result[entry.entity].name = entry.value.c;
break;
case "TYPE_HAS":
case "TYPE_REQUIRES":
case "TYPE_ID":
result[entry.entity].attributes.push(entry.value.c);
break;
}
});
return result;
});
const typedAttributes = reactive(
{} as { [key: string]: [string, IEntry][] }
);
const untypedAttributes = reactive([] as [string, IEntry][]);
watch([attributes, allTypes], () => {
Object.keys(typedAttributes).forEach(
(key) => delete typedAttributes[key]
);
untypedAttributes.length = 0;
attributes.value.forEach(([entryAddr, entry]) => {
const entryTypes = Object.entries(allTypes.value).filter(([_, t]) =>
t.attributes.includes(entry.attribute)
);
if (entryTypes.length > 0) {
entryTypes.forEach(([addr, _]) => {
if (typedAttributes[addr] == undefined) {
typedAttributes[addr] = [];
}
typedAttributes[addr].push([entryAddr, entry]);
});
} else {
untypedAttributes.push([entryAddr, entry]);
}
});
});
const filteredUntypedAttributes = computed(() => {
return untypedAttributes.filter(
([_, entry]) =>
entry.attribute !== "IS" ||
!Object.keys(typedAttributes).includes(entry.value.c)
);
});
return {
typedAttributes,
untypedAttributes: filteredUntypedAttributes,
backlinks,
error,
mutate,
types: allTypes,
};
},
});
</script>
<style lang="scss">
.inspect {
table {
width: 100%;
table-layout: fixed;
th {
text-align: left;
}
td {
font-family: var(--monospace-font);
padding-right: 1em;
line-height: 1em;
line-break: anywhere;
&.attr-action {
max-width: 1em;
}
}
.attr-action-col {
width: 1.5em;
}
sl-icon-button {
&::part(base) {
padding: 2px;
}
}
}
section {
position: relative;
overflow: visible;
margin-top: 1.66em;
padding: 1ex 1em;
border: 1px solid var(--foreground);
border-radius: 4px;
h3 {
display: inline-block;
background: var(--background);
font-weight: 600;
position: absolute;
top: -1.66em;
left: 1ex;
line-height: 1;
padding: 0 0.75ex;
sl-icon {
margin-bottom: -2px;
}
}
&.typed-attribute-list h3 a {
text-decoration: none;
}
}
.error {
color: red;
}
}
</style>