Newer
Older
bugtrail / packages / ui / src / ScreenshotViewer.vue
<script setup lang="ts">
import { ref } from "vue";
import type { AnnotationShape } from "@ltt/shared";
import AnnotationEditor from "./AnnotationEditor.vue";

const props = defineProps<{
  /** Image URL to display. */
  src: string;
  /** Existing annotation shapes (fractions of the screenshot). */
  shapes?: AnnotationShape[] | null;
  /** Whether the current user may edit annotations. */
  editable?: boolean;
  /** Button labels; defaults keep the component usable without an i18n setup. */
  labels?: { edit?: string; save?: string; saving?: string; cancel?: string };
}>();

const emit = defineEmits<{
  (e: "save", shapes: AnnotationShape[]): void;
}>();

const text = {
  edit: "Edit annotations",
  save: "Save annotations",
  saving: "Saving…",
  cancel: "Cancel",
};

const editing = ref(false);
const draft = ref<AnnotationShape[]>(props.shapes ?? []);
const editorRef = ref<InstanceType<typeof AnnotationEditor>>();
const saving = ref(false);

async function startEdit() {
  draft.value = [...(props.shapes ?? [])];
  editing.value = true;
}

async function save() {
  saving.value = true;
  try {
    emit("save", [...draft.value]);
  } finally {
    saving.value = false;
    editing.value = false;
  }
}
</script>

<template>
  <div class="screenshot-viewer">
    <AnnotationEditor
      v-if="editing"
      ref="editorRef"
      :src="src"
      v-model:shapes="draft"
      editable
    />
    <AnnotationEditor v-else :src="src" :shapes="shapes ?? []" />
    <div v-if="editable" class="screenshot-viewer-actions">
      <template v-if="editing">
        <button type="button" class="screenshot-btn" :disabled="saving" @click="save">
          {{ saving ? props.labels?.saving ?? text.saving : props.labels?.save ?? text.save }}
        </button>
        <button type="button" class="screenshot-btn-ghost" @click="editing = false">
          {{ props.labels?.cancel ?? text.cancel }}
        </button>
      </template>
      <button v-else type="button" class="screenshot-btn-ghost" @click="startEdit">
        <i class="ph ph-pen" /> {{ props.labels?.edit ?? text.edit }}
      </button>
    </div>
  </div>
</template>

<style scoped>
.screenshot-viewer {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}
.screenshot-viewer-actions {
  display: flex;
  gap: 8px;
}
.screenshot-btn-ghost,
.screenshot-btn-ghost + .screenshot-btn-ghost {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 2px solid var(--color-border, #2f334d);
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
}
.screenshot-btn-ghost:hover {
  border-color: var(--color-accent, #7aa2f7);
}
.screenshot-viewer-actions .screenshot-btn-ghost:first-child:not(.screenshot-btn-ghost + *) {
  border-color: var(--color-accent, #7aa2f7);
}
</style>