Skip to content
Open
2 changes: 1 addition & 1 deletion packages/web/app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ useServerSeoMeta({
description: 'Detect duplicate GitHub issues, areas of concern and more across related repositories',
})

const { data: repos } = useRepos()
const { data: repos } = useFetchRepos()
</script>

<template>
Expand Down
87 changes: 87 additions & 0 deletions packages/web/app/components/ClusterViewModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<script setup lang="ts">
const CLOSE = 'Close'

const dialogRef = useTemplateRef('dialogRef')

const isOpen = ref(false)

const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpen.value) {
closeModal()
}
}

const toggleBGScroll = (scrollLock: boolean) => {
if (scrollLock) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
}

const openModal = () => {
isOpen.value = true
toggleBGScroll(true)
}

const closeModal = () => {
isOpen.value = false
toggleBGScroll(false)
}

onMounted(() => {
document.addEventListener('keydown', handleEscape)
})

onUnmounted(() => {
document.removeEventListener('keydown', handleEscape)

if (isOpen.value) {
toggleBGScroll(false)
}
})

defineExpose({
openModal,
})
</script>

<template >
<Transition name="slide-fade">
<div
ref="dialogRef"
v-show="isOpen"
class="fixed inset-0 h-dvh w-dvw min-h-screen min-w-screen bg-gray-900/60 backdrop-blur-sm z-200"
>
<div
class="relative h-full w-full flex flex-col p-4 justify-center items-center"
>
<div class="modal-content bg-gray-800 border-solid border border-gray-700 rounded-md w-full max-w-3xl max-h-[80vh] overflow-y-auto p-8" >
<slot name="modal-content" />
</div>

<button class="text-xl bg-transparent color-gray-400 py-3 hover:color-gray-200 active:color-white focus:color-gray-200 hover:border-gray-400 active:border-white focus:border-gray-400 transition-colors" type="button" @click="closeModal" >
{{ CLOSE }}
</button>
</div>
</div>
</Transition >
</template>

<style >
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: all 0.3s ease;
}

.slide-fade-enter-from,
.slide-fade-leave-to {
transform: scale(1.1);
opacity: 0;
}

.modal-content {
scrollbar-color: #808080B3 transparent;
scrollbar-width: thin;
}
</style>
76 changes: 76 additions & 0 deletions packages/web/app/components/CommandConsole.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<script setup lang="ts">
import type { AsyncDataRequestStatus } from '#app'
import type { RepoMetadata } from '~~/shared/models/github-metadata'

interface CommandConsoleProps {
allowedRepos: RepoMetadata[];
status: AsyncDataRequestStatus;
selectedRepo: string;
}
const { allowedRepos, status, selectedRepo } = defineProps<CommandConsoleProps>()

const refresh = defineModel<() => Promise<void>>('refresh', {
required: true
})

const OWNER_REPO = 'owner-repo'
const REFRESH_DATA = 'refresh data'
const PICK_A_REPOSITORY = 'pick a repository to cluster issues'

function navigateToRepo(event: Event) {
const [owner, repo] = (event.target as HTMLSelectElement).value.split('/') as [string, string]
return navigateTo({
name: OWNER_REPO,
params: { owner, repo },
})
}
</script>

<!-- TODO: Can extract some of these further -->

<template>
<section class="w-full flex gap-2 justify-between justify-center items-center border-solid border border-gray-700 p-2 rounded-md">

<form class="flex gap-2 items-center" @submit.prevent="() => refresh()" >
<div class="flex gap-2 items-center">
<h2 class="text-base my-3 font-normal">
{{ selectedRepo }}
</h2>
<button
class="rounded-full w-7 h-7 flex items-center justify-center border-solid border border-gray-700 bg-transparent color-gray-400 hover:color-gray-200 active:color-white focus:color-gray-200 hover:border-gray-400 active:border-white focus:border-gray-400 transition-colors flex-shrink-0"
:class="{ 'animate-spin opacity-50 pointer-events-none': status === 'pending' || status === 'idle' }"
type="submit"
>
<span
size="medium"
class="text-gray-400 flex-shrink-0 i-tabler-refresh inline-block w-4 h-4"
/>
<span class="sr-only">{{ REFRESH_DATA }}</span>
</button>
</div>
<!-- TODO: Improve select button styling, maybe a full component -->
<!-- TODO: Use - https://developer.chrome.com/blog/a-customizable-select -->
<label class="w-full text-xs border-solid border border-gray-600 rounded-md flex flex-row items-center relative">
<span class="sr-only">{{ PICK_A_REPOSITORY }}</span>
<select
:value="selectedRepo"
class="pl-8 bg-shark-500 rounded-md pr-2 py-2 color-white border-0 w-full appearance-none"
@change="navigateToRepo"
>
<option
v-for="repo in allowedRepos.filter((repo: RepoMetadata) => repo.issuesIndexed > 0)"
:key="repo.repo"
:selected="repo.repo === selectedRepo"
>
{{ repo.repo }}
</option>
</select>
<span
class="absolute ml-2 text-gray-400 flex-shrink-0 i-tabler-search inline-block w-5 h-5"
/>
</label>
</form>

<RepoViewToggle />
</section>
</template>
69 changes: 69 additions & 0 deletions packages/web/app/components/DuplicatesView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

<script setup lang="ts">
import type { AsyncDataRequestStatus } from '#app'
import type { IssueMetadata } from '~~/shared/models/github-metadata'

interface DuplicatesrViewProps {
duplicates: IssueMetadata[];
status: AsyncDataRequestStatus;
}
const { duplicates, status } = defineProps<DuplicatesrViewProps>()

const LOADING_DUPLICATES = 'loading duplicates'
const POSSIBLE_DUPLICATE = 'possible duplicate'

const showDuplicates = ref(false)

const typedDuplicates = computed<IssueMetadata[]>(() => {
return showDuplicates ? duplicates.value?.map(cluster => cluster) : []}
)
</script>

<template >
<div
v-if="status !== 'success' && status !== 'error'"
class="mt-6 rounded-md border-solid border border-gray-700 bg-transparent color-gray-400 py-2 hover:color-gray-200 active:color-white focus:color-gray-200 hover:border-gray-400 active:border-white focus:border-gray-400 transition-colors flex items-center gap-2 justify-center pointer-events-none animate-pulse"
>
<span
size="medium"
class="text-gray-400 flex-shrink-0 i-tabler-refresh inline-block w-4 h-4 animate-spin"
/>
{{ LOADING_DUPLICATES }}
</div>

<button
v-else-if="!showDuplicates && duplicates.length"
class="mt-6 rounded-md border-solid border border-gray-700 bg-transparent color-gray-400 py-2 hover:color-gray-200 active:color-white focus:color-gray-200 hover:border-gray-400 active:border-white focus:border-gray-400 transition-colors flex items-center gap-2 justify-center w-full"
type="button"
@click="showDuplicates = true"
>
show {{ duplicates?.length }} possible duplicates
</button>
<template v-if="showDuplicates">
<section
v-for="(cluster, i) of typedDuplicates"
class="flex flex-col gap-4 md:rounded-md md:border-solid md:border md:border-gray-700 md:px-4 pb-8 mt-6 columns-1 lg:columns-2 flex-wrap border-b-solid"
>
<h2 class="my-4 font-bold text-2xl flex items-baseline">
<span class="text-gray-500 inline-block mr-1 font-normal">#</span>
<span>{{ i + 1 }}</span>
<span class="ml-auto text-white bg-gray-700 text-sm font-normal rounded-full px-2 py-0.5 whitespace-pre border-solid border-1 border-gray-700 inline-block leading-tight flex items-center">
<span class="i-tabler-wash-dryclean-off inline-block w-4 h-4"></span>
{{ POSSIBLE_DUPLICATE }}
</span>
</h2>
<GitHubIssue
v-for="(issue, j) of cluster"
:key="j"
:url="issue.url"
:title="issue.title"
:owner="issue.owner"
:repository="issue.repository"
:number="issue.number"
:avg-similarity="issue.score"
:labels="issue.labels"
:updated_at="issue.updated_at"
/>
</section>
</template>
</template>
9 changes: 5 additions & 4 deletions packages/web/app/components/GitHubIssue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ function labelColors(color: string) {
</script>

<template>
<article class="flex flex-row gap-2 leading-tightest">
<span class="flex-shrink-0 inline-block w-5 h-5 i-tabler-circle-dot text-green-500" />
<div class="flex flex-row gap-2 flex-wrap md:flex-nowrap md:pb-6 flex-grow">
<article class="border-solid border border-gray-600 rounded-md max-w-full overflow-hidden flex flex-row gap-2 leading-tightest ">
<span class="flex-shrink-0 inline-block h-5 i-tabler-circle-dot text-green-500" />
<div class="flex flex-col flex-wrap md:flex-nowrap md:pb-2 flex-grow">
<NuxtLink
class="line-clamp-1 flex-grow text-sm md:text-base lg:flex-grow-0 no-underline color-current hover:underline"
:href="url"
Expand All @@ -50,7 +50,7 @@ function labelColors(color: string) {
{{ title?.trim() }}
</NuxtLink>
<div
class="text-xs relative md:absolute md:mt-6 text-gray-400 mb-1"
class=" text-xs relative md:mt-1 text-gray-400 mb-1 break-words"
>
<NuxtLink
v-if="owner && repository"
Expand Down Expand Up @@ -97,6 +97,7 @@ function labelColors(color: string) {
</span>
</div>
</div>

</article>
</template>

Expand Down
2 changes: 2 additions & 0 deletions packages/web/app/components/GitHubIssueLoading.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<!-- TODO: Implement different loading isues for each view -->

<template>
<article>
<div class="flex flex-row gap-2 leading-tightest no-underline color-current">
Expand Down
17 changes: 17 additions & 0 deletions packages/web/app/components/GitHubIssueSkeleton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- TODO: Implement different skeletons for each view -->

<template >
<section
class="flex flex-col gap-4 md:rounded-md md:border-solid border border-gray-700 md:px-4 pb-8 mt-6 columns-1 lg:columns-2 border-b-solid animate-pulse"
>
<h2 class="flex items-center my-4 font-bold text-2xl">
<span class="text-gray-500 inline-block mr-1 font-normal">#</span>
<span class="inline-block rounded-md h-5 bg-gray-500 w-5" />
</h2>

<GitHubIssueLoading
v-for="s in Math.round(Math.random() * 3) + 1"
:key="s"
/>
</section>
</template>
21 changes: 21 additions & 0 deletions packages/web/app/components/NoClustersView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

<script setup lang="ts">
const NO_CLUSTERS = 'no clusters could be identified'
</script>

<template >
<section
class="flex flex-col gap-4 md:rounded-md md:border-solid border border-gray-700 md:px-4 pb-8 mt-6 columns-1 lg:columns-2 border-b-solid"
>
<h2 class="flex items-center my-4 font-bold text-2xl">
<span class="text-gray-500 inline-block mr-1 font-normal">#</span>
</h2>

<p class="flex flex-row gap-2 leading-tightest">
<span
class="flex-shrink-0 text-gray-400 i-tabler-alert-triangle inline-block w-5 h-5"
/>
{{ NO_CLUSTERS }}
</p>
</section>
</template>
48 changes: 48 additions & 0 deletions packages/web/app/components/RepoViewLayout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script setup lang="ts">
import type { ClusterMetadata } from '~~/shared/models/github-metadata'
import type { AsyncDataRequestStatus } from '#app'

interface RepoViewLayoutProps {
clusters: ClusterMetadata[];
status: AsyncDataRequestStatus;
}
const { clusters, status } = defineProps<RepoViewLayoutProps>()

const { selectedView } = useSelectedView()
</script>

<template>
<template v-if="status === 'idle' || status === 'pending'">
<div
v-for="i in 7"
:key="i"
>
<GitHubIssueSkeleton />
</div>
</template>

<template v-else-if="!clusters.length">
<NoClustersView />
</template>

<template v-else>
<template v-if="selectedView === 'PaperStack'">
<div class="grid grid-cols-4 gap-4 pt-4">
<PaperStackClusterLayout :clusters="clusters" />
</div>
</template>

<template v-else-if="selectedView === 'BookShelf'">
<div class="grid grid-cols-4 gap-4 pt-4">
<BookShelfClusterLayout :clusters="clusters" />
</div>
</template>

<template v-else-if="selectedView === 'WindowPane'" >
<div class="grid grid-cols-3 gap-4 pt-4">
<WindowPaneClusterLayout :clusters="clusters" />
</div>
</template>
</template>
</template>

Loading