Skip to content

Commit ebc7b3d

Browse files
IMB11AlexTMjugadoraecsocket
authored
feat: tech review frontend (#4781)
* chore: fix typo in status message * feat(labrinth): overhaul malware scanner report storage and routes * chore: address some review comments * feat: add Delphi to Docker Compose `with-delphi` profile * chore: fix unused import Clippy lint * feat(labrinth/delphi): use PAT token authorization with project read scopes * chore: expose file IDs in version queries * fix: accept null decompiled source payloads from Delphi * tweak(labrinth): expose base62 file IDs more consistently for Delphi * feat(labrinth/delphi): support new Delphi report severity field * chore(labrinth): run `cargo sqlx prepare` to fix Docker build errors * tweak: add route for fetching Delphi issue type schema, abstract Labrinth away from issue types * chore: run `cargo sqlx prepare` * chore: fix typo on frontend generated state file message * feat: update to use new Delphi issue schema * wip: tech review endpoints * wip: add ToSchema for dependent types * wip: report issues return * wip * wip: returning more data * wip * Fix up db query * Delphi configuration to talk to Labrinth * Get Delphi working with Labrinth * Add Delphi dummy fixture * Better Delphi logging * Improve utoipa for tech review routes * Add more sorting options for tech review queue * Oops join * New routes for fetching issues and reports * Fix which kind of ID is returned in tech review endpoints * Deduplicate tech review report rows * Reduce info sent for projects * Fetch more thread info * Address PR comments * fix ci * fix ci * fix postgres version mismatch * fix version creation * Implement routes * feat: batch scan alert * feat: layout * feat: introduce surface variables * fix: theme selector * feat: rough draft of tech review card * feat: tab switcher * feat: batch scan btn * feat: api-client module for tech review * draft: impl * feat: auto icons * fix: layout issues * feat: fixes to code blocks + flag labels * feat: temp remove mock data * fix: search sort types * fix: intl & lint * chore: re-enable mock data * fix: flag badges + auto open first issue in file tab * feat: update for new routes * fix: more qa issues * feat: lazy load sources * fix: re-enable auth middleware * feat: impl threads * fix: lint & severity * feat: download btn + switch to using NavTabs with new local mode option * feat: re-add toplevel btns * feat: reports page consistency * fix: consistency on project queue * fix: icons + sizing * fix: colors and gaps * fix: impl endpoints * feat: load all flags on file tab * feat: thread generics changes * feat: more qa * feat: fix collapse * fix: qa * feat: msg modal * fix: ISO import * feat: qa fixes * fix: empty state basic * fix: collapsible region * fix: collapse thread by default * feat: rough draft of new process/flow * fix labrinth build * fix thread message privacy * New tech review search route * feat: qa fixes * feat: QA changes * fix: verdict on detail not whole issue * fix: lint + intl * fix: lint * fix: thread message for tech rev verdict * feat: use anim frames * fix: exports + typecheck * polish: qa changes * feat: qa * feat: qa polish * feat: fix malic modal * fix: lint * fix: qa + lint * fix: pagination * fix: lint * fix: qa * intl extract * fix ci --------- Signed-off-by: Calum H. <contact@cal.engineer> Co-authored-by: Alejandro González <me@alegon.dev> Co-authored-by: aecsocket <aecsocket@tutanota.com>
1 parent f9a6eb3 commit ebc7b3d

File tree

61 files changed

+3756
-1720
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+3756
-1720
lines changed

apps/frontend/src/components/ui/NavTabs.vue

Lines changed: 126 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,58 @@
11
<template>
22
<nav
33
ref="scrollContainer"
4-
class="card-shadow experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
4+
class="experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
5+
:class="[mode === 'navigation' ? 'card-shadow' : undefined]"
56
>
6-
<NuxtLink
7-
v-for="(link, index) in filteredLinks"
8-
v-show="link.shown === undefined ? true : link.shown"
9-
:key="index"
10-
ref="tabLinkElements"
11-
:to="query ? (link.href ? `?${query}=${link.href}` : '?') : link.href"
12-
class="button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full"
13-
:class="{
14-
'text-button-textSelected': activeIndex === index && !subpageSelected,
15-
'text-contrast': activeIndex === index && subpageSelected,
16-
}"
17-
>
18-
<component :is="link.icon" v-if="link.icon" class="size-5" />
19-
<span class="text-nowrap">{{ link.label }}</span>
20-
</NuxtLink>
7+
<template v-if="mode === 'navigation'">
8+
<NuxtLink
9+
v-for="(link, index) in filteredLinks"
10+
v-show="link.shown === undefined ? true : link.shown"
11+
:key="link.href"
12+
ref="tabLinkElements"
13+
:to="query ? (link.href ? `?${query}=${link.href}` : '?') : link.href"
14+
class="button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full"
15+
>
16+
<component
17+
:is="link.icon"
18+
v-if="link.icon"
19+
class="size-5"
20+
:class="{
21+
'text-brand': currentActiveIndex === index && !subpageSelected,
22+
'text-secondary': currentActiveIndex !== index || subpageSelected,
23+
}"
24+
/>
25+
<span class="text-nowrap text-contrast">{{ link.label }}</span>
26+
</NuxtLink>
27+
</template>
28+
<template v-else>
29+
<div
30+
v-for="(link, index) in filteredLinks"
31+
v-show="link.shown === undefined ? true : link.shown"
32+
:key="link.href"
33+
ref="tabLinkElements"
34+
class="button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 hover:cursor-pointer focus:rounded-full"
35+
@click="emit('tabClick', index, link)"
36+
>
37+
<component
38+
:is="link.icon"
39+
v-if="link.icon"
40+
class="size-5"
41+
:class="{
42+
'text-brand': currentActiveIndex === index && !subpageSelected,
43+
'text-secondary': currentActiveIndex !== index || subpageSelected,
44+
}"
45+
/>
46+
<span
47+
class="text-nowrap"
48+
:class="{
49+
'text-brand': currentActiveIndex === index && !subpageSelected,
50+
'text-contrast': currentActiveIndex !== index || subpageSelected,
51+
}"
52+
>{{ link.label }}</span
53+
>
54+
</div>
55+
</template>
2156
<div
2257
:class="`navtabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1 ${
2358
subpageSelected ? 'bg-button-bg' : 'bg-button-bgSelected'
@@ -27,29 +62,44 @@
2762
top: sliderTopPx,
2863
right: sliderRightPx,
2964
bottom: sliderBottomPx,
30-
opacity: sliderLeft === 4 && sliderLeft === sliderRight ? 0 : activeIndex === -1 ? 0 : 1,
65+
opacity:
66+
sliderLeft === 4 && sliderLeft === sliderRight ? 0 : currentActiveIndex === -1 ? 0 : 1,
3167
}"
3268
aria-hidden="true"
3369
></div>
3470
</nav>
3571
</template>
3672

3773
<script setup lang="ts">
38-
import { computed, onMounted, ref, watch } from 'vue'
74+
import type { Component } from 'vue'
75+
import { computed, nextTick, onMounted, ref, watch } from 'vue'
3976
4077
const route = useNativeRoute()
4178
4279
interface Tab {
4380
label: string
4481
href: string
4582
shown?: boolean
46-
icon?: string
83+
icon?: Component
4784
subpages?: string[]
4885
}
4986
50-
const props = defineProps<{
51-
links: Tab[]
52-
query?: string
87+
const props = withDefaults(
88+
defineProps<{
89+
links: Tab[]
90+
query?: string
91+
mode?: 'navigation' | 'local'
92+
activeIndex?: number
93+
}>(),
94+
{
95+
mode: 'navigation',
96+
query: undefined,
97+
activeIndex: undefined,
98+
},
99+
)
100+
101+
const emit = defineEmits<{
102+
tabClick: [index: number, tab: Tab]
53103
}>()
54104
55105
const scrollContainer = ref<HTMLElement | null>(null)
@@ -58,7 +108,7 @@ const sliderLeft = ref(4)
58108
const sliderTop = ref(4)
59109
const sliderRight = ref(4)
60110
const sliderBottom = ref(4)
61-
const activeIndex = ref(-1)
111+
const currentActiveIndex = ref(-1)
62112
const subpageSelected = ref(false)
63113
64114
const filteredLinks = computed(() =>
@@ -74,38 +124,49 @@ const tabLinkElements = ref()
74124
function pickLink() {
75125
let index = -1
76126
subpageSelected.value = false
77-
for (let i = filteredLinks.value.length - 1; i >= 0; i--) {
78-
const link = filteredLinks.value[i]
79-
if (props.query) {
80-
if (route.query[props.query] === link.href || (!route.query[props.query] && !link.href)) {
127+
128+
if (props.mode === 'local' && props.activeIndex !== undefined) {
129+
index = Math.min(props.activeIndex, filteredLinks.value.length - 1)
130+
} else {
131+
for (let i = filteredLinks.value.length - 1; i >= 0; i--) {
132+
const link = filteredLinks.value[i]
133+
if (props.query) {
134+
if (route.query[props.query] === link.href || (!route.query[props.query] && !link.href)) {
135+
index = i
136+
break
137+
}
138+
} else if (decodeURIComponent(route.path) === link.href) {
139+
index = i
140+
break
141+
} else if (
142+
decodeURIComponent(route.path).includes(link.href) ||
143+
(link.subpages &&
144+
link.subpages.some((subpage) => decodeURIComponent(route.path).includes(subpage)))
145+
) {
81146
index = i
147+
subpageSelected.value = true
82148
break
83149
}
84-
} else if (decodeURIComponent(route.path) === link.href) {
85-
index = i
86-
break
87-
} else if (
88-
decodeURIComponent(route.path).includes(link.href) ||
89-
(link.subpages &&
90-
link.subpages.some((subpage) => decodeURIComponent(route.path).includes(subpage)))
91-
) {
92-
index = i
93-
subpageSelected.value = true
94-
break
95150
}
96151
}
97-
activeIndex.value = index
98152
99-
if (activeIndex.value !== -1) {
100-
startAnimation()
153+
currentActiveIndex.value = index
154+
155+
if (currentActiveIndex.value !== -1) {
156+
nextTick(() => startAnimation())
101157
} else {
102158
sliderLeft.value = 0
103159
sliderRight.value = 0
104160
}
105161
}
106162
107163
function startAnimation() {
108-
const el = tabLinkElements.value[activeIndex.value]?.$el
164+
// In navigation mode, elements are NuxtLinks with $el property
165+
// In local mode, elements are plain divs
166+
const el =
167+
props.mode === 'navigation'
168+
? tabLinkElements.value[currentActiveIndex.value]?.$el
169+
: tabLinkElements.value[currentActiveIndex.value]
109170
110171
if (!el || !el.offsetParent) return
111172
@@ -156,7 +217,29 @@ onMounted(() => {
156217
157218
watch(
158219
() => [route.path, route.query],
159-
() => pickLink(),
220+
() => {
221+
if (props.mode === 'navigation') {
222+
pickLink()
223+
}
224+
},
225+
)
226+
227+
watch(
228+
() => props.activeIndex,
229+
() => {
230+
if (props.mode === 'local') {
231+
pickLink()
232+
}
233+
},
234+
)
235+
236+
watch(
237+
() => props.links,
238+
() => {
239+
// Re-trigger animation when links change
240+
pickLink()
241+
},
242+
{ deep: true },
160243
)
161244
</script>
162245

apps/frontend/src/components/ui/dashboard/withdraw-stages/MuralpayDetailsStage.vue

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ import {
207207
financialMessages,
208208
formFieldLabels,
209209
formFieldPlaceholders,
210+
getBlockchainColor,
211+
getBlockchainIcon,
210212
normalizeChildren,
211213
} from '@modrinth/ui'
212214
import { defineMessages, useVIntl } from '@vintl/vintl'
@@ -218,12 +220,6 @@ import RevenueInputField from '@/components/ui/dashboard/RevenueInputField.vue'
218220
import WithdrawFeeBreakdown from '@/components/ui/dashboard/WithdrawFeeBreakdown.vue'
219221
import { useGeneratedState } from '@/composables/generated'
220222
import { useWithdrawContext } from '@/providers/creator-withdraw.ts'
221-
import {
222-
getBlockchainColor,
223-
getBlockchainIcon,
224-
getCurrencyColor,
225-
getCurrencyIcon,
226-
} from '@/utils/finance-icons.ts'
227223
import { getRailConfig } from '@/utils/muralpay-rails'
228224
229225
const { withdrawData, maxWithdrawAmount, availableMethods, calculateFees } = useWithdrawContext()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<template>
2+
<div
3+
class="flex flex-col gap-4 rounded-2xl border-[1px] border-solid border-blue bg-highlight-blue p-4"
4+
>
5+
<div class="flex flex-row justify-between">
6+
<div class="flex flex-col text-contrast">
7+
<span class="text-xl font-semibold">Batch scan in progress</span>
8+
<span>{{ progress?.complete }} of {{ progress?.total }} projects completed</span>
9+
</div>
10+
<ButtonStyled circular color="blue" type="outlined">
11+
<button class="!px-4" @click="emit('cancel-scan')">Cancel scan</button>
12+
</ButtonStyled>
13+
</div>
14+
<div class="w-full rounded-full bg-highlight-blue">
15+
<div
16+
class="h-3 rounded-[inherit] bg-blue"
17+
:style="`width: ${((progress?.complete ?? 0) / (progress?.total ?? 1)) * 100}%`"
18+
/>
19+
</div>
20+
</div>
21+
</template>
22+
23+
<script setup lang="ts">
24+
import { ButtonStyled } from '@modrinth/ui'
25+
import { defineProps } from 'vue'
26+
27+
export interface BatchScanProgress {
28+
total: number
29+
complete: number
30+
}
31+
32+
defineProps<{
33+
progress?: BatchScanProgress
34+
}>()
35+
36+
const emit = defineEmits<{
37+
(e: 'cancel-scan'): void
38+
}>()
39+
</script>

0 commit comments

Comments
 (0)