Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ import { Admonition, Avatar, Button, Input, NewModal } from '@modrinth/ui'
import { computed, ref } from 'vue'

const props = defineProps<{
organization: { name: string; icon_url: string | null }
organization: { name: string; icon_url: string | null; raw_icon_url?: string | null }
currentOwner: { avatar_url: string | null; username: string; role: string }
transferTo: { avatar_url: string | null; username: string; role: string }
onConfirm: () => void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ import { Admonition, Avatar, Button, Input, NewModal } from '@modrinth/ui'
import { computed, ref } from 'vue'

const props = defineProps<{
project: { name: string; icon_url: string | null }
project: { name: string; icon_url: string | null; raw_icon_url?: string | null }
currentOwner: { avatar_url: string | null; username: string; role: string }
transferTo: {
avatar_url?: string | null
Expand Down
8 changes: 3 additions & 5 deletions apps/frontend/src/pages/[type]/[project]/gallery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,10 @@
<div v-if="filteredGallery.length" class="items">
<div v-for="(item, index) in filteredGallery" :key="index" class="card gallery-item">
<a class="gallery-thumbnail" @click="expandImage(index)">
<img
<FullImage
:src="item.url ? item.url : 'https://cdn.modrinth.com/placeholder-banner.svg'"
:raw-src="item.raw_url"
:alt="item.title ? item.title : 'gallery-image'"
@contextmenu="onFullImageContextMenu($event, item.raw_url)"
/>
</a>
<div class="gallery-body">
Expand Down Expand Up @@ -252,13 +252,13 @@ import {
ConfirmModal,
DropArea,
FileButton,
FullImage,
ImageViewerEditor,
injectProjectPageContext,
Input,
NewModal as Modal,
Textarea,
useFormatDateTime,
useFullImageContextMenu,
} from '@modrinth/ui'

import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue'
Expand All @@ -272,8 +272,6 @@ const formatDate = useFormatDateTime({
month: 'long',
day: 'numeric',
})
const onFullImageContextMenu = useFullImageContextMenu()

// Single DI injection
const {
projectV2: project,
Expand Down
8 changes: 3 additions & 5 deletions apps/frontend/src/pages/[type]/[project]/settings/gallery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,10 @@
<div class="items">
<div v-for="(item, index) in filteredGallery" :key="index" class="card gallery-item">
<a class="gallery-thumbnail" @click="expandImage(item, index)">
<img
<FullImage
:src="item.url ? item.url : 'https://cdn.modrinth.com/placeholder-banner.svg'"
:raw-src="item.raw_url"
:alt="item.title ? item.title : 'gallery-image'"
@contextmenu="onFullImageContextMenu($event, item.raw_url)"
/>
</a>
<div class="gallery-body">
Expand Down Expand Up @@ -307,13 +307,13 @@ import {
ConfirmModal,
DropArea,
FileButton,
FullImage,
IconButton,
injectProjectPageContext,
Input,
NewModal as Modal,
Textarea,
useFormatDateTime,
useFullImageContextMenu,
} from '@modrinth/ui'

import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue'
Expand All @@ -327,8 +327,6 @@ const formatDate = useFormatDateTime({
month: 'long',
day: 'numeric',
})
const onFullImageContextMenu = useFullImageContextMenu()

const {
projectV2: project,
currentMember,
Expand Down
3 changes: 3 additions & 0 deletions apps/labrinth/src/models/v3/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub struct Collection {

/// An icon URL for the collection.
pub icon_url: Option<String>,
/// The raw icon URL for the collection.
pub raw_icon_url: Option<String>,
/// Color of the collection.
pub color: Option<u32>,

Expand Down Expand Up @@ -46,6 +48,7 @@ impl From<database::models::DBCollection> for Collection {
updated: c.updated,
projects: c.projects.into_iter().map(|x| x.into()).collect(),
icon_url: c.icon_url,
raw_icon_url: c.raw_icon_url,
color: c.color,
status: c.status,
}
Expand Down
3 changes: 3 additions & 0 deletions apps/labrinth/src/models/v3/organizations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub struct Organization {

/// The icon url of the organization
pub icon_url: Option<String>,
/// The raw icon url of the organization
pub raw_icon_url: Option<String>,
/// The color of the organization (picked from the icon)
pub color: Option<u32>,

Expand All @@ -41,6 +43,7 @@ impl Organization {
description: data.description,
members: team_members,
icon_url: data.icon_url,
raw_icon_url: data.raw_icon_url,
color: data.color,
moderation_notes: None,
}
Expand Down
1 change: 1 addition & 0 deletions apps/labrinth/src/routes/v3/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ pub async fn collection_create(
created: now,
updated: now,
icon_url: None,
raw_icon_url: None,
color: None,
status: collection_builder.status,
projects: initial_project_ids,
Expand Down
13 changes: 7 additions & 6 deletions packages/ui/src/components/base/Avatar.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<img
<FullImage
v-if="src && !failed"
ref="img"
ref="imgComponent"
class="avatar shrink-0"
:style="`--_size: ${cssSize}`"
:class="{
Expand All @@ -13,11 +13,11 @@
pixelated: pixelated,
}"
:src="src"
:raw-src="rawSrc"
:alt="alt"
:loading="loading"
@load="onLoad"
@error="onError"
@contextmenu="onFullImageContextMenu($event, rawSrc)"
/>
<svg
v-else
Expand Down Expand Up @@ -51,15 +51,16 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'

import { useDebugLogger, useFullImageContextMenu } from '../../composables'
import { useDebugLogger } from '../../composables'
import FullImage from './FullImage.vue'

const onFullImageContextMenu = useFullImageContextMenu()
const debug = useDebugLogger('Avatar')

const pixelated = ref(false)
const hasTransparentCorners = ref(false)
const hasDetectedCorners = ref(false)
const img = useTemplateRef<HTMLImageElement>('img')
const imgComponent = useTemplateRef<InstanceType<typeof FullImage>>('imgComponent')
const img = computed(() => imgComponent.value?.img ?? null)
const failed = ref(false)
let detectionTimeout: number | undefined
let detectingSource: string | undefined
Expand Down
70 changes: 70 additions & 0 deletions packages/ui/src/components/base/FullImage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<img ref="img" :src="src" v-bind="$attrs" @contextmenu="onContextMenu" @dragstart="onDragStart" />
</template>

<script setup lang="ts">
import { useTemplateRef } from 'vue'

defineOptions({
inheritAttrs: false,
})

const props = defineProps<{
src?: string | null
rawSrc?: string | null
}>()

const img = useTemplateRef<HTMLImageElement>('img')

defineExpose({
img,
})

function swapImageSrc(
imgEl: HTMLImageElement,
fullUrl: string,
revertTargets: { target: EventTarget; type: string; capture?: boolean }[],
) {
const originalSrc = imgEl.src
if (originalSrc === fullUrl) return

imgEl.src = fullUrl

let reverted = false
const revert = () => {
if (reverted) return
reverted = true
imgEl.src = originalSrc
for (const { target, type, capture } of revertTargets) {
target.removeEventListener(type, revert, capture)
}
}

for (const { target, type, capture } of revertTargets) {
target.addEventListener(type, revert, capture)
}
}

function onContextMenu(event: MouseEvent) {
if (!props.rawSrc) return

swapImageSrc(event.currentTarget as HTMLImageElement, props.rawSrc, [
{ target: window, type: 'focus' },
{ target: window, type: 'pointerdown', capture: true },
{ target: window, type: 'keydown', capture: true },
{ target: window, type: 'pointermove', capture: true },
{ target: window, type: 'wheel', capture: true },
{ target: window, type: 'touchstart', capture: true },
])
}

function onDragStart(event: DragEvent) {
if (!props.rawSrc || !event.dataTransfer) return

const filename = props.rawSrc.split('/').pop()?.split('?')[0] || 'image'

event.dataTransfer.setData('DownloadURL', `application/octet-stream:${filename}:${props.rawSrc}`)
event.dataTransfer.setData('text/uri-list', props.rawSrc)
event.dataTransfer.setData('text/plain', props.rawSrc)
}
</script>
1 change: 1 addition & 0 deletions packages/ui/src/components/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export { default as FilterPills } from './FilterPills.vue'
export { default as FloatingActionBar } from './FloatingActionBar.vue'
export { default as FloatingPanel } from './FloatingPanel.vue'
export { default as FormattedTag } from './FormattedTag.vue'
export { default as FullImage } from './FullImage.vue'
export { default as HeadingLink } from './HeadingLink.vue'
export { default as HorizontalRule } from './HorizontalRule.vue'
export { default as I18nDebugPanel } from './I18nDebugPanel.vue'
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/project/ProjectPageHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type HeaderProject = Pick<
'id' | 'title' | 'description' | 'status' | 'downloads' | 'followers' | 'categories'
> & {
icon_url?: string | null
raw_icon_url?: string | null
}

type HeaderProjectV3 = Pick<
Expand Down
31 changes: 0 additions & 31 deletions packages/ui/src/composables/full-image-context-menu.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/ui/src/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export * from './format-bytes'
export * from './format-date-time'
export * from './format-money'
export * from './format-number'
export * from './full-image-context-menu'
export * from './hosting-intercom'
export * from './how-ago'
export * from './i18n'
Expand Down
Loading