254 lines
5.9 KiB
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>
|