Merge branch 'main' of gitlab.com:agenceminuit/musicland
This commit is contained in:
+38
-20
@@ -32,40 +32,56 @@ const normalizeArtistName = (value) => {
|
|||||||
return trimmedValue || 'MusicLand'
|
return trimmedValue || 'MusicLand'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getArtistFontSize = (artistName) => {
|
const normalizeSongTitle = (value) => {
|
||||||
if (artistName.length <= 16) return 52
|
if (typeof value !== 'string') {
|
||||||
if (artistName.length <= 24) return 44
|
return 'Sans titre'
|
||||||
|
}
|
||||||
|
const trimmedValue = value.trim()
|
||||||
|
return trimmedValue || 'Sans titre'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTitleFontSize = (songTitle) => {
|
||||||
|
if (songTitle.length <= 16) return 38
|
||||||
|
if (songTitle.length <= 24) return 32
|
||||||
|
return 26
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUsernameFontSize = (username) => {
|
||||||
|
if (username.length <= 16) return 52
|
||||||
|
if (username.length <= 24) return 44
|
||||||
return 36
|
return 36
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildTextOverlay = (artistName) => {
|
const buildTextOverlay = ({ songTitle, username }) => {
|
||||||
const safeArtistName = escapeSvgText(artistName)
|
const safeSongTitle = escapeSvgText(songTitle)
|
||||||
const artistFontSize = getArtistFontSize(artistName)
|
const safeUsername = escapeSvgText(username)
|
||||||
|
const titleFontSize = getTitleFontSize(songTitle)
|
||||||
|
const usernameFontSize = getUsernameFontSize(username)
|
||||||
|
|
||||||
return Buffer.from(`
|
return Buffer.from(`
|
||||||
<svg width="${COVER_SIZE}" height="${COVER_SIZE}" viewBox="0 0 ${COVER_SIZE} ${COVER_SIZE}" xmlns="http://www.w3.org/2000/svg">
|
<svg width="${COVER_SIZE}" height="${COVER_SIZE}" viewBox="0 0 ${COVER_SIZE} ${COVER_SIZE}" xmlns="http://www.w3.org/2000/svg">
|
||||||
<text
|
<text
|
||||||
x="512"
|
x="512"
|
||||||
y="824"
|
y="170"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
dominant-baseline="middle"
|
dominant-baseline="middle"
|
||||||
font-family="${FONT_FAMILY}"
|
font-family="${FONT_FAMILY}"
|
||||||
font-size="${artistFontSize}"
|
font-size="${titleFontSize}"
|
||||||
font-weight="400"
|
font-weight="400"
|
||||||
letter-spacing="3"
|
letter-spacing="2"
|
||||||
fill="#FFFFFF"
|
fill="#FFFFFF"
|
||||||
>${safeArtistName}</text>
|
>${safeSongTitle}</text>
|
||||||
<text
|
<text
|
||||||
x="512"
|
x="512"
|
||||||
y="963"
|
y="963"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
dominant-baseline="middle"
|
dominant-baseline="middle"
|
||||||
font-family="${FONT_FAMILY}"
|
font-family="${FONT_FAMILY}"
|
||||||
font-size="54"
|
font-size="${usernameFontSize}"
|
||||||
font-weight="400"
|
font-weight="400"
|
||||||
letter-spacing="5"
|
letter-spacing="4"
|
||||||
fill="#FFFFFF"
|
fill="#FFFFFF"
|
||||||
>PLAYBACKER</text>
|
>${safeUsername}</text>
|
||||||
</svg>`)
|
</svg>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,26 +106,28 @@ const buildCircularMask = (size) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadCoverIdentity(project) {
|
async function loadCoverIdentity(project) {
|
||||||
const artistName = normalizeArtistName(project.userName)
|
const username = normalizeArtistName(project.userName)
|
||||||
|
const songTitle = normalizeSongTitle(project.title)
|
||||||
const userId = typeof project.userId === 'string' ? project.userId.trim() : ''
|
const userId = typeof project.userId === 'string' ? project.userId.trim() : ''
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return { artistName, profilePictureURL: null }
|
return { songTitle, username, profilePictureURL: project.coverCenterImageURL || null }
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userSnapshot = await refList.users.doc(userId).get()
|
const userSnapshot = await refList.users.doc(userId).get()
|
||||||
const userData = userSnapshot.exists ? userSnapshot.data() : null
|
const userData = userSnapshot.exists ? userSnapshot.data() : null
|
||||||
return {
|
return {
|
||||||
artistName: normalizeArtistName(project.userName || userData?.userName),
|
songTitle,
|
||||||
profilePictureURL: userData?.profilePictureURL || null,
|
username: normalizeArtistName(project.userName || userData?.userName),
|
||||||
|
profilePictureURL: project.coverCenterImageURL || userData?.profilePictureURL || null,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('[Cover] Unable to load profile picture', {
|
logger.warn('[Cover] Unable to load profile picture', {
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
})
|
})
|
||||||
return { artistName, profilePictureURL: null }
|
return { songTitle, username, profilePictureURL: project.coverCenterImageURL || null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,9 +187,9 @@ async function prepareMusiclandCover(project) {
|
|||||||
throw new Error('Utilisateur du projet introuvable')
|
throw new Error('Utilisateur du projet introuvable')
|
||||||
}
|
}
|
||||||
|
|
||||||
const { artistName, profilePictureURL } = await loadCoverIdentity(project)
|
const { songTitle, username, profilePictureURL } = await loadCoverIdentity(project)
|
||||||
const profileOverlay = await buildProfileOverlay(profilePictureURL)
|
const profileOverlay = await buildProfileOverlay(profilePictureURL)
|
||||||
const overlays = [{ input: buildTextOverlay(artistName), left: 0, top: 0 }]
|
const overlays = [{ input: buildTextOverlay({ songTitle, username }), left: 0, top: 0 }]
|
||||||
|
|
||||||
if (profileOverlay) {
|
if (profileOverlay) {
|
||||||
overlays.unshift({
|
overlays.unshift({
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 488 KiB |
@@ -5,13 +5,16 @@ import { img } from '../assets'
|
|||||||
import { FONT_FAMILY } from '../styles/Fonts'
|
import { FONT_FAMILY } from '../styles/Fonts'
|
||||||
|
|
||||||
const MusiclandPlaybackCover = ({
|
const MusiclandPlaybackCover = ({
|
||||||
artistName,
|
songTitle,
|
||||||
|
username,
|
||||||
profilePictureURL,
|
profilePictureURL,
|
||||||
coverUrl,
|
coverUrl,
|
||||||
style,
|
style,
|
||||||
}) => {
|
}) => {
|
||||||
const displayName =
|
const displayTitle =
|
||||||
typeof artistName === 'string' && artistName.trim() ? artistName.trim() : 'MusicLand'
|
typeof songTitle === 'string' && songTitle.trim() ? songTitle.trim() : 'Sans titre'
|
||||||
|
const displayUsername =
|
||||||
|
typeof username === 'string' && username.trim() ? username.trim() : 'MusicLand'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, style]}>
|
<View style={[styles.container, style]}>
|
||||||
@@ -36,11 +39,18 @@ const MusiclandPlaybackCover = ({
|
|||||||
adjustsFontSizeToFit
|
adjustsFontSizeToFit
|
||||||
minimumFontScale={0.55}
|
minimumFontScale={0.55}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
style={styles.artistName}
|
style={styles.songTitle}
|
||||||
>
|
>
|
||||||
{displayName}
|
{displayTitle}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
adjustsFontSizeToFit
|
||||||
|
minimumFontScale={0.55}
|
||||||
|
numberOfLines={1}
|
||||||
|
style={styles.username}
|
||||||
|
>
|
||||||
|
{displayUsername}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.playbacker}>PLAYBACKER</Text>
|
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
@@ -63,19 +73,19 @@ const styles = StyleSheet.create({
|
|||||||
top: '39.84375%',
|
top: '39.84375%',
|
||||||
borderRadius: 999,
|
borderRadius: 999,
|
||||||
},
|
},
|
||||||
artistName: {
|
songTitle: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '76.5%',
|
top: '15%',
|
||||||
left: '16%',
|
left: '10%',
|
||||||
width: '68%',
|
width: '80%',
|
||||||
color: '#FFFFFF',
|
color: '#FFFFFF',
|
||||||
fontFamily: FONT_FAMILY.InterRegular,
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
fontSize: 21,
|
fontSize: 14,
|
||||||
lineHeight: 26,
|
lineHeight: 18,
|
||||||
letterSpacing: 1.2,
|
letterSpacing: 0.8,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
playbacker: {
|
username: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: '3%',
|
bottom: '3%',
|
||||||
left: '10%',
|
left: '10%',
|
||||||
|
|||||||
@@ -330,7 +330,8 @@ export const ChooseCoverType = () => {
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.previewContainer}>
|
<View style={styles.previewContainer}>
|
||||||
<MusiclandPlaybackCover
|
<MusiclandPlaybackCover
|
||||||
artistName={selectedProject?.userName || currentUserData?.userName || 'MusicLand'}
|
songTitle={selectedProject?.title}
|
||||||
|
username={selectedProject?.userName || currentUserData?.userName || 'MusicLand'}
|
||||||
profilePictureURL={
|
profilePictureURL={
|
||||||
selectedProject?.coverCenterImageURL || currentUserData?.profilePictureURL || null
|
selectedProject?.coverCenterImageURL || currentUserData?.profilePictureURL || null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,11 @@ const PouchReady = () => {
|
|||||||
const coverUrl = templateCover?.result || null
|
const coverUrl = templateCover?.result || null
|
||||||
const isPreparing = selectedProject?.coverStatus === 'GENERATING'
|
const isPreparing = selectedProject?.coverStatus === 'GENERATING'
|
||||||
const hasPreparationError = selectedProject?.coverStatus === 'ERROR'
|
const hasPreparationError = selectedProject?.coverStatus === 'ERROR'
|
||||||
const artistName =
|
const username =
|
||||||
typeof selectedProject?.userName === 'string' && selectedProject.userName.trim()
|
typeof selectedProject?.userName === 'string' && selectedProject.userName.trim()
|
||||||
? selectedProject.userName.trim()
|
? selectedProject.userName.trim()
|
||||||
: currentUserData?.userName || 'MusicLand'
|
: currentUserData?.userName || 'MusicLand'
|
||||||
|
const songTitle = selectedProject?.title || 'Sans titre'
|
||||||
const profilePictureURL = currentUserData?.profilePictureURL || null
|
const profilePictureURL = currentUserData?.profilePictureURL || null
|
||||||
const centerImageURL = selectedProject?.coverCenterImageURL || profilePictureURL
|
const centerImageURL = selectedProject?.coverCenterImageURL || profilePictureURL
|
||||||
|
|
||||||
@@ -119,7 +120,8 @@ const PouchReady = () => {
|
|||||||
throw new Error('cover_project_identity_missing')
|
throw new Error('cover_project_identity_missing')
|
||||||
}
|
}
|
||||||
const coverBlob = await createMusiclandPlaybackCover({
|
const coverBlob = await createMusiclandPlaybackCover({
|
||||||
artistName,
|
songTitle,
|
||||||
|
username,
|
||||||
profilePictureURL: centerImageURL,
|
profilePictureURL: centerImageURL,
|
||||||
})
|
})
|
||||||
const storagePath = `users/${userId}/projects/${selectedProjectId}/cover-musicland-playbacker.png`
|
const storagePath = `users/${userId}/projects/${selectedProjectId}/cover-musicland-playbacker.png`
|
||||||
@@ -170,13 +172,14 @@ const PouchReady = () => {
|
|||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
artistName,
|
|
||||||
coverUrl,
|
coverUrl,
|
||||||
centerImageURL,
|
centerImageURL,
|
||||||
selectedOption,
|
selectedOption,
|
||||||
selectedProject,
|
selectedProject,
|
||||||
selectedProjectId,
|
selectedProjectId,
|
||||||
|
songTitle,
|
||||||
updateProjectData,
|
updateProjectData,
|
||||||
|
username,
|
||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -193,7 +196,8 @@ const PouchReady = () => {
|
|||||||
/>
|
/>
|
||||||
<View style={[styles.previewArea, Style.containerCenter]}>
|
<View style={[styles.previewArea, Style.containerCenter]}>
|
||||||
<MusiclandPlaybackCover
|
<MusiclandPlaybackCover
|
||||||
artistName={artistName}
|
songTitle={songTitle}
|
||||||
|
username={username}
|
||||||
profilePictureURL={centerImageURL}
|
profilePictureURL={centerImageURL}
|
||||||
coverUrl={coverUrl}
|
coverUrl={coverUrl}
|
||||||
style={isWeb ? styles.coverWeb : styles.coverMobile}
|
style={isWeb ? styles.coverWeb : styles.coverMobile}
|
||||||
|
|||||||
@@ -28,9 +28,15 @@ const drawCenteredText = (context, text, y, fontSize, letterSpacing) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getArtistFontSize = (artistName) => {
|
const getTitleFontSize = (songTitle) => {
|
||||||
if (artistName.length <= 16) return 52
|
if (songTitle.length <= 16) return 38
|
||||||
if (artistName.length <= 24) return 44
|
if (songTitle.length <= 24) return 32
|
||||||
|
return 26
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUsernameFontSize = (username) => {
|
||||||
|
if (username.length <= 16) return 52
|
||||||
|
if (username.length <= 24) return 44
|
||||||
return 36
|
return 36
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +73,7 @@ const drawProfilePicture = (context, profilePicture) => {
|
|||||||
context.restore()
|
context.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createMusiclandPlaybackCover = async ({ artistName, profilePictureURL }) => {
|
export const createMusiclandPlaybackCover = async ({ songTitle, username, profilePictureURL }) => {
|
||||||
const templateAsset = Asset.fromModule(img.musiclandPlaybackCover)
|
const templateAsset = Asset.fromModule(img.musiclandPlaybackCover)
|
||||||
await templateAsset.downloadAsync()
|
await templateAsset.downloadAsync()
|
||||||
const assetUri = templateAsset.localUri || templateAsset.uri
|
const assetUri = templateAsset.localUri || templateAsset.uri
|
||||||
@@ -91,10 +97,12 @@ export const createMusiclandPlaybackCover = async ({ artistName, profilePictureU
|
|||||||
|
|
||||||
context.fillStyle = '#FFFFFF'
|
context.fillStyle = '#FFFFFF'
|
||||||
context.textBaseline = 'middle'
|
context.textBaseline = 'middle'
|
||||||
context.font = `400 ${getArtistFontSize(artistName)}px Inter, Arial, sans-serif`
|
const titleFontSize = getTitleFontSize(songTitle)
|
||||||
drawCenteredText(context, artistName, 824, getArtistFontSize(artistName), 3)
|
context.font = `400 ${titleFontSize}px Inter, Arial, sans-serif`
|
||||||
context.font = '400 54px Inter, Arial, sans-serif'
|
drawCenteredText(context, songTitle, 170, titleFontSize, 2)
|
||||||
drawCenteredText(context, 'PLAYBACKER', 963, 54, 5)
|
const usernameFontSize = getUsernameFontSize(username)
|
||||||
|
context.font = `400 ${usernameFontSize}px Inter, Arial, sans-serif`
|
||||||
|
drawCenteredText(context, username, 963, usernameFontSize, 4)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
canvas.toBlob((blob) => {
|
canvas.toBlob((blob) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user