feat(renderer): add bounded session image element
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'SessionImage' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
src: { type: String, required: true },
|
||||||
|
alt: { type: String, default: '' },
|
||||||
|
title: { type: String, default: '' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const status = ref('loading');
|
||||||
|
const accessibleLabel = computed(() => props.alt || props.title || 'Session image');
|
||||||
|
|
||||||
|
function handleLoad() {
|
||||||
|
status.value = 'loaded';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError() {
|
||||||
|
status.value = 'error';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<figure
|
||||||
|
class="session-image"
|
||||||
|
:class="`is-${status}`"
|
||||||
|
:aria-busy="status === 'loading' ? 'true' : undefined"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="status !== 'error'"
|
||||||
|
:src="src"
|
||||||
|
:alt="alt"
|
||||||
|
:title="title || undefined"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
@load="handleLoad"
|
||||||
|
@error="handleError"
|
||||||
|
>
|
||||||
|
<figcaption v-else role="status">
|
||||||
|
<span>Image unavailable</span>
|
||||||
|
<span v-if="accessibleLabel" class="image-label">{{ accessibleLabel }}</span>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
max-inline-size: 100%;
|
||||||
|
min-inline-size: 0;
|
||||||
|
margin: 0.7em 0;
|
||||||
|
contain: inline-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-image {
|
||||||
|
max-inline-size: 100%;
|
||||||
|
min-inline-size: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: clip;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(0, 0, 0, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
inline-size: auto;
|
||||||
|
max-inline-size: 100%;
|
||||||
|
block-size: auto;
|
||||||
|
max-block-size: min(70vh, 720px);
|
||||||
|
object-fit: contain;
|
||||||
|
color: rgba(255, 255, 255, 0.48);
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-loading {
|
||||||
|
min-block-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption {
|
||||||
|
display: flex;
|
||||||
|
min-block-size: 48px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.48);
|
||||||
|
font: 12px/1.5 ui-monospace, 'SF Mono', Menlo, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-label::before {
|
||||||
|
content: '·';
|
||||||
|
margin-inline-end: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,6 +6,7 @@ import router from './router.js';
|
|||||||
import { commitInitialData, fetchInitialData } from './data.js';
|
import { commitInitialData, fetchInitialData } from './data.js';
|
||||||
import { noteSessionUpdated, sessionLiveState } from './session-live.mjs';
|
import { noteSessionUpdated, sessionLiveState } from './session-live.mjs';
|
||||||
import { createGlobalDataRefreshCoordinator } from './session-global-refresh.mjs';
|
import { createGlobalDataRefreshCoordinator } from './session-global-refresh.mjs';
|
||||||
|
import { registerSessionImageElement } from './session-image-element.js';
|
||||||
|
|
||||||
// Import shared renderer CSS globally
|
// Import shared renderer CSS globally
|
||||||
import '../styles/base.css';
|
import '../styles/base.css';
|
||||||
@@ -14,6 +15,8 @@ import '../styles/toolbar.css';
|
|||||||
import '../styles/list.css';
|
import '../styles/list.css';
|
||||||
import '../styles/detail.css';
|
import '../styles/detail.css';
|
||||||
|
|
||||||
|
registerSessionImageElement();
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineCustomElement } from 'vue';
|
||||||
|
import SessionImage from './components/SessionImage.ce.vue';
|
||||||
|
|
||||||
|
export const SESSION_IMAGE_TAG = 'obelisk-session-image';
|
||||||
|
|
||||||
|
export function registerSessionImageElement() {
|
||||||
|
if (customElements.get(SESSION_IMAGE_TAG)) return;
|
||||||
|
customElements.define(SESSION_IMAGE_TAG, defineCustomElement(SessionImage));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user