New Canvas zoom with dbl click and starting to work on tag API
This commit is contained in:
parent
c091a6d261
commit
2b293a0c1a
|
|
@ -10,11 +10,26 @@ const props = defineProps<Props>();
|
|||
let dragging = false, posX = 0, posY = 0, dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
|
||||
|
||||
let lastDistance = 0;
|
||||
let lastClickTime = 0;
|
||||
|
||||
const onPointerDown = (event: PointerEvent) => {
|
||||
if (event.isPrimary === false) return;
|
||||
dragging = true;
|
||||
|
||||
const now = performance.now();
|
||||
if(now - lastClickTime < 500 && Math.abs(event.clientX - posX) < 20 && Math.abs(event.clientY - posY) < 20)
|
||||
{
|
||||
if(event.ctrlKey)
|
||||
{
|
||||
zoom.value = clamp(zoom.value * 0.9, minZoom.value, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
zoom.value = clamp(zoom.value * 1.1, minZoom.value, 3);
|
||||
}
|
||||
}
|
||||
lastClickTime = now;
|
||||
|
||||
posX = event.clientX;
|
||||
posY = event.clientY;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<Teleport to="#teleports" v-if="display && (!fetched || loaded)">
|
||||
<div class="absolute border-2 border-light-35 dark:border-dark-35 max-w-[550px] max-h-[450px] bg-light-0 dark:bg-dark-0 text-light-100 dark:text-dark-100" :class="[{'is-loaded': fetched}, type === 'Markdown' ? 'overflow-auto' : 'overflow-hidden']" :style="pos"
|
||||
<div class="absolute border-2 border-light-35 dark:border-dark-35 max-w-[550px] max-h-[450px] bg-light-0 dark:bg-dark-0 text-light-100 dark:text-dark-100" :class="[{'is-loaded': fetched}, file?.type === 'Markdown' ? 'overflow-auto' : 'overflow-hidden']" :style="pos"
|
||||
@mouseenter="debounce(show, 250)" @mouseleave="debounce(() => display = false, 250)">
|
||||
<div v-if="pending" class="loading"></div>
|
||||
<template v-else-if="content !==''">
|
||||
<div v-if="type === 'Markdown'" class="p-6 ms-6">
|
||||
<ProseH1>{{ title }}</ProseH1>
|
||||
<Markdown v-model="content"></Markdown>
|
||||
<template v-else-if="!!file">
|
||||
<div v-if="file.type === 'Markdown'" class="p-6 ms-6">
|
||||
<ProseH1>{{ file.title }}</ProseH1>
|
||||
<Markdown v-model="file.content"></Markdown>
|
||||
</div>
|
||||
<div v-else-if="type === 'Canvas'" class="w-[550px] h-[450px] overflow-hidden">
|
||||
<CanvasRenderer :canvas="JSON.parse(content) " />
|
||||
<div v-else-if="file.type === 'Canvas'" class="w-[550px] h-[450px] overflow-hidden">
|
||||
<CanvasRenderer :canvas="JSON.parse(file.content) " />
|
||||
</div>
|
||||
<div class="h-100 w-100 flex flex-1 flex-col justify-center items-center" v-else>
|
||||
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Fichier vide</div>
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CommentedFile } from '~/types/api';
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
|
|
@ -43,30 +44,20 @@ const props = defineProps({
|
|||
required: false,
|
||||
}
|
||||
})
|
||||
const content = ref(''), title = ref(''), type = ref(''), display = ref(false), pending = ref(false), fetched = ref(false);
|
||||
const file = ref<CommentedFile | null>();
|
||||
const display = ref(false), pending = ref(false), fetched = ref(false);
|
||||
const el = ref(), pos = ref<Record<string, string>>();
|
||||
const loaded = computed(() => !pending.value && fetched.value && content.value !== '');
|
||||
const loaded = computed(() => !pending.value && fetched.value && file.value !== undefined);
|
||||
|
||||
async function fetch()
|
||||
{
|
||||
fetched.value = true;
|
||||
pending.value = true;
|
||||
|
||||
const data = await $fetch(`/api/project/${props.project}/file`, {
|
||||
method: 'get',
|
||||
query: {
|
||||
path: props.path,
|
||||
},
|
||||
});
|
||||
const { data } = await useFetch(`/api/project/${props.project}/file/${encodeURIComponent(props.path)}`);
|
||||
|
||||
pending.value = false;
|
||||
|
||||
if(data && data[0])
|
||||
{
|
||||
content.value = data[0].content;
|
||||
title.value = data[0].title;
|
||||
type.value = data[0].type;
|
||||
}
|
||||
file.value = data.value;
|
||||
}
|
||||
async function show()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ const { data, status } = await useFetch(`/api/project/${project.value}/file`, {
|
|||
},
|
||||
transform: (data) => data?.map(e => ({ path: e.path, type: e.type })),
|
||||
key: `file:${project.value}:%${pathname}`,
|
||||
dedupe: 'defer'
|
||||
dedupe: 'defer',
|
||||
ignoreResponseError: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,182 @@
|
|||
<template>
|
||||
<span v-if="focused">> </span>
|
||||
<blockquote ref="el" @focusin="focused = true" @focusout="focused = false">
|
||||
<slot />
|
||||
</blockquote>
|
||||
<blockquote ref="el">
|
||||
<slot />
|
||||
</blockquote>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const focused = ref(false);
|
||||
const attrs = useAttrs(), el = ref<HTMLQuoteElement>(), title = ref<Element | null>(null);
|
||||
|
||||
watch(focused, console.log);
|
||||
onMounted(() => {
|
||||
if(el && el.value && attrs.hasOwnProperty("dataCalloutFold"))
|
||||
{
|
||||
title.value = el.value.querySelector('.callout-title');
|
||||
title.value?.addEventListener('click', toggle);
|
||||
}
|
||||
});
|
||||
onUnmounted(() => {
|
||||
title.value?.removeEventListener('click', toggle);
|
||||
})
|
||||
function toggle() {
|
||||
el.value?.classList?.toggle('is-collapsed');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
blockquote:not(.callout)
|
||||
{
|
||||
@apply ps-4;
|
||||
@apply my-4;
|
||||
@apply relative;
|
||||
@apply before:absolute;
|
||||
@apply before:-top-1;
|
||||
@apply before:-bottom-1;
|
||||
@apply before:left-0;
|
||||
@apply before:w-1;
|
||||
@apply before:bg-light-30;
|
||||
@apply dark:before:bg-dark-30;
|
||||
}
|
||||
blockquote:empty
|
||||
{
|
||||
@apply before:hidden;
|
||||
}
|
||||
.callout {
|
||||
@apply bg-light-blue;
|
||||
@apply dark:bg-dark-blue;
|
||||
}
|
||||
.callout.is-collapsible .callout-title
|
||||
{
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
.callout .fold
|
||||
{
|
||||
@apply transition-transform;
|
||||
}
|
||||
.callout.is-collapsed .fold
|
||||
{
|
||||
@apply -rotate-90;
|
||||
}
|
||||
.callout.is-collapsed > p
|
||||
{
|
||||
@apply hidden;
|
||||
}
|
||||
.callout[datacallout="abstract"],
|
||||
.callout[datacallout="summary"],
|
||||
.callout[datacallout="tldr"] {
|
||||
@apply bg-light-cyan;
|
||||
@apply dark:bg-dark-cyan;
|
||||
@apply text-light-cyan;
|
||||
@apply dark:text-dark-cyan;
|
||||
}
|
||||
.callout[datacallout="info"] {
|
||||
@apply bg-light-blue;
|
||||
@apply dark:bg-dark-blue;
|
||||
@apply text-light-blue;
|
||||
@apply dark:text-dark-blue;
|
||||
}
|
||||
.callout[datacallout="todo"] {
|
||||
@apply bg-light-blue;
|
||||
@apply dark:bg-dark-blue;
|
||||
@apply text-light-blue;
|
||||
@apply dark:text-dark-blue;
|
||||
}
|
||||
.callout[datacallout="important"] {
|
||||
@apply bg-light-cyan;
|
||||
@apply dark:bg-dark-cyan;
|
||||
@apply text-light-cyan;
|
||||
@apply dark:text-dark-cyan;
|
||||
}
|
||||
.callout[datacallout="tip"],
|
||||
.callout[datacallout="hint"] {
|
||||
@apply bg-light-cyan;
|
||||
@apply dark:bg-dark-cyan;
|
||||
@apply text-light-cyan;
|
||||
@apply dark:text-dark-cyan;
|
||||
}
|
||||
.callout[datacallout="success"],
|
||||
.callout[datacallout="check"],
|
||||
.callout[datacallout="done"] {
|
||||
@apply bg-light-green;
|
||||
@apply dark:bg-dark-green;
|
||||
@apply text-light-green;
|
||||
@apply dark:text-dark-green;
|
||||
}
|
||||
.callout[datacallout="question"],
|
||||
.callout[datacallout="help"],
|
||||
.callout[datacallout="faq"] {
|
||||
@apply bg-light-orange;
|
||||
@apply dark:bg-dark-orange;
|
||||
@apply text-light-orange;
|
||||
@apply dark:text-dark-orange;
|
||||
}
|
||||
.callout[datacallout="warning"],
|
||||
.callout[datacallout="caution"],
|
||||
.callout[datacallout="attention"] {
|
||||
@apply bg-light-orange;
|
||||
@apply dark:bg-dark-orange;
|
||||
@apply text-light-orange;
|
||||
@apply dark:text-dark-orange;
|
||||
}
|
||||
.callout[datacallout="failure"],
|
||||
.callout[datacallout="fail"],
|
||||
.callout[datacallout="missing"] {
|
||||
@apply bg-light-red;
|
||||
@apply dark:bg-dark-red;
|
||||
@apply text-light-red;
|
||||
@apply dark:text-dark-red;
|
||||
}
|
||||
.callout[datacallout="danger"],
|
||||
.callout[datacallout="error"] {
|
||||
@apply bg-light-red;
|
||||
@apply dark:bg-dark-red;
|
||||
@apply text-light-red;
|
||||
@apply dark:text-dark-red;
|
||||
}
|
||||
.callout[datacallout="bug"] {
|
||||
@apply bg-light-red;
|
||||
@apply dark:bg-dark-red;
|
||||
@apply text-light-red;
|
||||
@apply dark:text-dark-red;
|
||||
}
|
||||
.callout[datacallout="example"] {
|
||||
@apply bg-light-purple;
|
||||
@apply dark:bg-dark-purple;
|
||||
@apply text-light-purple;
|
||||
@apply dark:text-dark-purple;
|
||||
}
|
||||
|
||||
.callout
|
||||
{
|
||||
@apply overflow-hidden;
|
||||
@apply my-4;
|
||||
@apply p-3;
|
||||
@apply ps-6;
|
||||
@apply bg-blend-lighten;
|
||||
@apply !bg-opacity-25;
|
||||
@apply border-l-4;
|
||||
@apply inline-block;
|
||||
@apply pe-8;
|
||||
}
|
||||
.callout-icon
|
||||
{
|
||||
@apply w-6;
|
||||
@apply h-6;
|
||||
@apply stroke-2;
|
||||
}
|
||||
.callout-title
|
||||
{
|
||||
@apply flex;
|
||||
@apply items-center;
|
||||
@apply gap-2;
|
||||
}
|
||||
.callout-title-inner
|
||||
{
|
||||
@apply inline-block;
|
||||
@apply font-bold;
|
||||
}
|
||||
.callout > p
|
||||
{
|
||||
@apply mt-2;
|
||||
@apply font-semibold;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
<template>
|
||||
<span v-show="false"># </span><h1><slot></slot></h1>
|
||||
<h1 :id="parseId(id)" class="text-5xl font-thin mt-3 mb-8 first:pt-0 pt-2 relative sm:right-8 right-4">
|
||||
<slot />
|
||||
</h1>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ id?: string }>()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
<template>
|
||||
<span v-show="focused">## </span><h2><slot></slot></h2>
|
||||
<h2 :id="parseId(id)" class="text-4xl font-semibold mt-3 mb-6 ms-1 first:pt-0 pt-2 relative sm:right-8 right-4">
|
||||
<slot />
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
focused: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const generate = computed(() => props.id)
|
||||
</script>
|
||||
|
|
@ -1,3 +1,11 @@
|
|||
<template>
|
||||
<span v-show="false">### </span><h3><slot></slot></h3>
|
||||
<h3 :id="parseId(id)" class="text-2xl font-bold mt-2 mb-4">
|
||||
<slot />
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const generate = computed(() => props.id)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
<template>
|
||||
<span v-show="false">#### </span><h4><slot></slot></h4>
|
||||
<h4 :id="parseId(id)" class="text-xl font-semibold my-2" style="font-variant: small-caps;">
|
||||
<slot />
|
||||
</h4>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ id?: string }>()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
<template>
|
||||
<span v-show="false">##### </span><h5><slot></slot></h5>
|
||||
<h5 :id="parseId(id)" class="text-lg font-semibold my-1">
|
||||
<slot />
|
||||
</h5>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const generate = computed(() => props.id)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
<template>
|
||||
<span v-show="false">###### </span><h6><slot></slot></h6>
|
||||
<h6 :id="parseId(id)">
|
||||
<slot />
|
||||
</h6>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const generate = computed(() => props.id)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function toggleNavigation(bool?: boolean)
|
|||
<p>Copyright Peaceultime - 2024</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 flex items-baseline overflow-auto p-8 relative">
|
||||
<div class="flex-1 flex items-baseline overflow-auto py-8 sm:px-8 px-4 relative">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
import useDatabase from '~/composables/useDatabase';
|
||||
import { Comment } from '~/types/api';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const project = getRouterParam(e, "projectId");
|
||||
const query = getQuery(e);
|
||||
|
||||
if(!project || !query.path)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const where = ["project = $project", "path = $path"];
|
||||
const criteria: Record<string, any> = { $project: project, $path: query.path };
|
||||
|
||||
if(where.length > 1)
|
||||
{
|
||||
const db = useDatabase();
|
||||
|
||||
const content = db.query(`SELECT * FROM explorer_comments WHERE ${where.join(" and ")}`).all(criteria) as Comment[];
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
});
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import useDatabase from '~/composables/useDatabase';
|
||||
import type { CommentedFile, CommentSearch, File } from '~/types/api';
|
||||
|
||||
export default defineCachedEventHandler(async (e) => {
|
||||
const project = getRouterParam(e, "projectId");
|
||||
const path = decodeURIComponent(getRouterParam(e, "path") ?? '');
|
||||
|
||||
if(!project)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
if(!path)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const where = ["project = $project", "path = $path"];
|
||||
const criteria: Record<string, any> = { $project: project, $path: path };
|
||||
|
||||
if(where.length > 1)
|
||||
{
|
||||
const db = useDatabase();
|
||||
|
||||
const content = db.query(`SELECT * FROM explorer_files WHERE ${where.join(" and ")}`).get(criteria) as File;
|
||||
|
||||
if(content !== undefined)
|
||||
{
|
||||
const comments = db.query(`SELECT comment.*, user.username FROM explorer_comments comment LEFT JOIN users user ON comment.user_id = user.id WHERE ${where.join(" and ")}`).all(criteria) as CommentSearch[];
|
||||
|
||||
return { ...content, comments } as CommentedFile;
|
||||
}
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}, {
|
||||
maxAge: 60*60*24,
|
||||
getKey: (e) => `file-${getRouterParam(e, "projectId")}-${getRouterParam(e, "path")}`
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import useDatabase from '~/composables/useDatabase';
|
||||
import type { Tag } from '~/types/api';
|
||||
|
||||
export default defineCachedEventHandler(async (e) => {
|
||||
const project = getRouterParam(e, "projectId");
|
||||
const tag = getRouterParam(e, "tag");
|
||||
const query = getQuery(e);
|
||||
|
||||
if(!project)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
if(!tag)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const where = ["project = $project", "tag = $tag"];
|
||||
const criteria: Record<string, any> = { $project: project, $tag: tag };
|
||||
|
||||
if(where.length > 1)
|
||||
{
|
||||
const db = useDatabase();
|
||||
|
||||
const content = db.query(`SELECT * FROM explorer_tags WHERE ${where.join(" and ")}`).get(criteria) as Tag;
|
||||
|
||||
if(content !== undefined)
|
||||
{
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
setResponseStatus(e, 404);
|
||||
}, {
|
||||
maxAge: 60*60*24,
|
||||
getKey: (e) => `tag-${getRouterParam(e, "projectId")}-${getRouterParam(e, "tag")}`
|
||||
});
|
||||
|
|
@ -52,6 +52,13 @@ export interface User {
|
|||
id: number;
|
||||
username: string;
|
||||
}
|
||||
export interface Tag {
|
||||
tag: string;
|
||||
project: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
|
||||
export type ProjectSearch = Project &
|
||||
{
|
||||
pages: number;
|
||||
|
|
@ -69,6 +76,10 @@ export type CommentSearch = Comment &
|
|||
export type UserSearch = User &
|
||||
{
|
||||
}
|
||||
export type CommentedFile = File &
|
||||
{
|
||||
comments: CommentSearch[];
|
||||
}
|
||||
export interface Search {
|
||||
projects: ProjectSearch[];
|
||||
files: FileSearch[];
|
||||
|
|
|
|||
Loading…
Reference in New Issue