feat: fixes and formatter
This commit is contained in:
+22
-22
@@ -1,24 +1,24 @@
|
||||
import { registerSheet } from "react-native-actions-sheet";
|
||||
import DeleteModal from "../components/modal/DeleteModal";
|
||||
import ProfileSettingsModal from "../components/modal/ProfileSettingsModal";
|
||||
import DeleteAccountModal from "../components/modal/DeleteAccountModal";
|
||||
import DeletePlaybackModal from "../components/modal/DeletePlaybackModal";
|
||||
import DeleteAudioModal from "../components/modal/DeleteAudioModal";
|
||||
import PlaylistModal from "../components/modal/PlaylistModal";
|
||||
import PlaybackPickerModal from "../components/modal/PlaybackPickerModal";
|
||||
import ShareModal from "../components/modal/ShareModal";
|
||||
import ReportModal from "../components/modal/ReportModal";
|
||||
import MusicOptionsModal from "../components/modal/MusicOptionsModal";
|
||||
import { registerSheet } from 'react-native-actions-sheet'
|
||||
import DeleteModal from '../components/modal/DeleteModal'
|
||||
import ProfileSettingsModal from '../components/modal/ProfileSettingsModal'
|
||||
import DeleteAccountModal from '../components/modal/DeleteAccountModal'
|
||||
import DeletePlaybackModal from '../components/modal/DeletePlaybackModal'
|
||||
import DeleteAudioModal from '../components/modal/DeleteAudioModal'
|
||||
import PlaylistModal from '../components/modal/PlaylistModal'
|
||||
import PlaybackPickerModal from '../components/modal/PlaybackPickerModal'
|
||||
import ShareModal from '../components/modal/ShareModal'
|
||||
import ReportModal from '../components/modal/ReportModal'
|
||||
import MusicOptionsModal from '../components/modal/MusicOptionsModal'
|
||||
|
||||
registerSheet("Delete", DeleteModal);
|
||||
registerSheet("ProfileSettings", ProfileSettingsModal);
|
||||
registerSheet("DeleteAccount", DeleteAccountModal);
|
||||
registerSheet("DeletePlayback", DeletePlaybackModal);
|
||||
registerSheet("DeleteAudio", DeleteAudioModal);
|
||||
registerSheet("Playlist", PlaylistModal);
|
||||
registerSheet("PlaybackPicker", PlaybackPickerModal);
|
||||
registerSheet("Share", ShareModal);
|
||||
registerSheet("Report", ReportModal);
|
||||
registerSheet("MusicOptions", MusicOptionsModal);
|
||||
registerSheet('Delete', DeleteModal)
|
||||
registerSheet('ProfileSettings', ProfileSettingsModal)
|
||||
registerSheet('DeleteAccount', DeleteAccountModal)
|
||||
registerSheet('DeletePlayback', DeletePlaybackModal)
|
||||
registerSheet('DeleteAudio', DeleteAudioModal)
|
||||
registerSheet('Playlist', PlaylistModal)
|
||||
registerSheet('PlaybackPicker', PlaybackPickerModal)
|
||||
registerSheet('Share', ShareModal)
|
||||
registerSheet('Report', ReportModal)
|
||||
registerSheet('MusicOptions', MusicOptionsModal)
|
||||
|
||||
export {};
|
||||
export {}
|
||||
|
||||
+18
-27
@@ -1,39 +1,30 @@
|
||||
export const buildFullName = ({ firstName, lastName, displayName } = {}) => {
|
||||
const first =
|
||||
typeof firstName === "string" && firstName.trim().length > 0
|
||||
? firstName.trim()
|
||||
: "";
|
||||
const last =
|
||||
typeof lastName === "string" && lastName.trim().length > 0
|
||||
? lastName.trim()
|
||||
: "";
|
||||
const nameFromParts = [first, last].filter(Boolean).join(" ").trim();
|
||||
if (nameFromParts) return nameFromParts;
|
||||
const first = typeof firstName === 'string' && firstName.trim().length > 0 ? firstName.trim() : ''
|
||||
const last = typeof lastName === 'string' && lastName.trim().length > 0 ? lastName.trim() : ''
|
||||
const nameFromParts = [first, last].filter(Boolean).join(' ').trim()
|
||||
if (nameFromParts) return nameFromParts
|
||||
|
||||
const fallbackDisplay =
|
||||
typeof displayName === "string" && displayName.trim().length > 0
|
||||
? displayName.trim()
|
||||
: "";
|
||||
return fallbackDisplay || null;
|
||||
};
|
||||
typeof displayName === 'string' && displayName.trim().length > 0 ? displayName.trim() : ''
|
||||
return fallbackDisplay || null
|
||||
}
|
||||
|
||||
export const getArtistDisplayName = (source, fallback = "") => {
|
||||
if (!source) return fallback;
|
||||
const rawUserName =
|
||||
typeof source?.userName === "string" ? source.userName.trim() : "";
|
||||
if (rawUserName) return rawUserName;
|
||||
export const getArtistDisplayName = (source, fallback = '') => {
|
||||
if (!source) return fallback
|
||||
const rawUserName = typeof source?.userName === 'string' ? source.userName.trim() : ''
|
||||
if (rawUserName) return rawUserName
|
||||
|
||||
const nameFromParts = buildFullName({
|
||||
firstName: source?.firstName,
|
||||
lastName: source?.lastName,
|
||||
displayName: source?.displayName,
|
||||
});
|
||||
})
|
||||
|
||||
if (nameFromParts) return nameFromParts;
|
||||
return fallback;
|
||||
};
|
||||
if (nameFromParts) return nameFromParts
|
||||
return fallback
|
||||
}
|
||||
|
||||
export const getUserPreferredArtistName = (user) => {
|
||||
const display = getArtistDisplayName(user, "");
|
||||
return display || null;
|
||||
};
|
||||
const display = getArtistDisplayName(user, '')
|
||||
return display || null
|
||||
}
|
||||
|
||||
+20
-20
@@ -1,40 +1,40 @@
|
||||
import { getGlobal } from "reactn";
|
||||
import { navigate } from "../navigation/NavigationService";
|
||||
import { Routes } from "../navigation/Routes";
|
||||
import { getGlobal } from 'reactn'
|
||||
import { navigate } from '../navigation/NavigationService'
|
||||
import { Routes } from '../navigation/Routes'
|
||||
|
||||
const redirectToRegister = () => {
|
||||
try {
|
||||
const activeRoute = getGlobal()?.activeRouteName;
|
||||
const activeRoute = getGlobal()?.activeRouteName
|
||||
if (activeRoute === Routes.Register) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("authRedirect: unable to read active route", error);
|
||||
console.warn('authRedirect: unable to read active route', error)
|
||||
}
|
||||
navigate(Routes.Register);
|
||||
};
|
||||
navigate(Routes.Register)
|
||||
}
|
||||
|
||||
export const ensureAuthenticated = (currentUID, options = {}) => {
|
||||
const { onIntercept } = options || {};
|
||||
const { onIntercept } = options || {}
|
||||
if (currentUID) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
try {
|
||||
if (typeof onIntercept === "function") {
|
||||
onIntercept();
|
||||
if (typeof onIntercept === 'function') {
|
||||
onIntercept()
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("authRedirect: onIntercept error", error);
|
||||
console.warn('authRedirect: onIntercept error', error)
|
||||
}
|
||||
redirectToRegister();
|
||||
return false;
|
||||
};
|
||||
redirectToRegister()
|
||||
return false
|
||||
}
|
||||
|
||||
export const withAuthGuard = async (currentUID, handler, options = {}) => {
|
||||
if (!ensureAuthenticated(currentUID, options)) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
return handler?.();
|
||||
};
|
||||
return handler?.()
|
||||
}
|
||||
|
||||
export default ensureAuthenticated;
|
||||
export default ensureAuthenticated
|
||||
|
||||
+23
-23
@@ -1,48 +1,48 @@
|
||||
const globalScope =
|
||||
typeof globalThis !== "undefined"
|
||||
typeof globalThis !== 'undefined'
|
||||
? globalThis
|
||||
: typeof global !== "undefined"
|
||||
: typeof global !== 'undefined'
|
||||
? global
|
||||
: typeof window !== "undefined"
|
||||
: typeof window !== 'undefined'
|
||||
? window
|
||||
: undefined;
|
||||
: undefined
|
||||
|
||||
const hasObjectUrlSupport = Boolean(
|
||||
globalScope?.URL && typeof globalScope.URL.createObjectURL === "function"
|
||||
);
|
||||
globalScope?.URL && typeof globalScope.URL.createObjectURL === 'function'
|
||||
)
|
||||
|
||||
const REGISTRY_KEY = "__musiclandBlobRegistry";
|
||||
const REGISTRY_KEY = '__musiclandBlobRegistry'
|
||||
|
||||
const blobRegistry = (() => {
|
||||
if (globalScope && globalScope[REGISTRY_KEY] instanceof Map) {
|
||||
return globalScope[REGISTRY_KEY];
|
||||
return globalScope[REGISTRY_KEY]
|
||||
}
|
||||
const registry = new Map();
|
||||
const registry = new Map()
|
||||
if (globalScope) {
|
||||
try {
|
||||
globalScope[REGISTRY_KEY] = registry;
|
||||
globalScope[REGISTRY_KEY] = registry
|
||||
} catch {}
|
||||
}
|
||||
return registry;
|
||||
})();
|
||||
return registry
|
||||
})()
|
||||
|
||||
const isValidBlobUrl = (url) =>
|
||||
hasObjectUrlSupport && typeof url === "string" && url.startsWith("blob:");
|
||||
hasObjectUrlSupport && typeof url === 'string' && url.startsWith('blob:')
|
||||
|
||||
export const registerBlobUrl = (url, blob) => {
|
||||
if (!isValidBlobUrl(url) || !blob) return;
|
||||
blobRegistry.set(url, blob);
|
||||
};
|
||||
if (!isValidBlobUrl(url) || !blob) return
|
||||
blobRegistry.set(url, blob)
|
||||
}
|
||||
|
||||
export const getBlobForUrl = (url) => {
|
||||
if (!isValidBlobUrl(url)) return null;
|
||||
return blobRegistry.get(url) || null;
|
||||
};
|
||||
if (!isValidBlobUrl(url)) return null
|
||||
return blobRegistry.get(url) || null
|
||||
}
|
||||
|
||||
export const releaseBlobUrl = (url) => {
|
||||
if (!isValidBlobUrl(url)) return;
|
||||
blobRegistry.delete(url);
|
||||
if (!isValidBlobUrl(url)) return
|
||||
blobRegistry.delete(url)
|
||||
try {
|
||||
globalScope.URL.revokeObjectURL(url);
|
||||
globalScope.URL.revokeObjectURL(url)
|
||||
} catch {}
|
||||
};
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,21 +1,21 @@
|
||||
const listeners = new Set();
|
||||
const listeners = new Set()
|
||||
|
||||
export const subscribeCoinPackModal = (listener) => {
|
||||
if (typeof listener !== "function") {
|
||||
return () => {};
|
||||
if (typeof listener !== 'function') {
|
||||
return () => {}
|
||||
}
|
||||
listeners.add(listener);
|
||||
listeners.add(listener)
|
||||
return () => {
|
||||
listeners.delete(listener);
|
||||
};
|
||||
};
|
||||
listeners.delete(listener)
|
||||
}
|
||||
}
|
||||
|
||||
export const openCoinPackModal = () => {
|
||||
listeners.forEach((listener) => {
|
||||
try {
|
||||
listener();
|
||||
listener()
|
||||
} catch (error) {
|
||||
console.error("[coinPackModal] open listener error", error);
|
||||
console.error('[coinPackModal] open listener error', error)
|
||||
}
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
+23
-23
@@ -1,44 +1,44 @@
|
||||
const toDate = (value) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
if (typeof value.toDate === "function") {
|
||||
if (typeof value.toDate === 'function') {
|
||||
try {
|
||||
return value.toDate();
|
||||
return value.toDate()
|
||||
} catch (_error) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return value;
|
||||
return value
|
||||
}
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
if (value > 1e12) {
|
||||
return new Date(value);
|
||||
return new Date(value)
|
||||
}
|
||||
return new Date(value * 1000);
|
||||
return new Date(value * 1000)
|
||||
}
|
||||
if (typeof value === "object" && Number.isFinite(value.seconds)) {
|
||||
return new Date(value.seconds * 1000);
|
||||
if (typeof value === 'object' && Number.isFinite(value.seconds)) {
|
||||
return new Date(value.seconds * 1000)
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return null
|
||||
}
|
||||
|
||||
const formatDate = (date) => {
|
||||
if (!(date instanceof Date)) {
|
||||
return "À déterminer";
|
||||
return 'À déterminer'
|
||||
}
|
||||
try {
|
||||
return new Intl.DateTimeFormat("fr-FR", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(date);
|
||||
return new Intl.DateTimeFormat('fr-FR', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).format(date)
|
||||
} catch (_error) {
|
||||
return date.toString();
|
||||
return date.toString()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { toDate, formatDate };
|
||||
export { toDate, formatDate }
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
import QRCode from "qrcode-terminal/vendor/QRCode";
|
||||
import QRErrorCorrectLevel from "qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel";
|
||||
import QRCode from 'qrcode-terminal/vendor/QRCode'
|
||||
import QRErrorCorrectLevel from 'qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel'
|
||||
|
||||
const getErrorLevel = (level) => {
|
||||
if (typeof level === "number") {
|
||||
return level;
|
||||
if (typeof level === 'number') {
|
||||
return level
|
||||
}
|
||||
|
||||
const normalized = String(level || "M").toUpperCase();
|
||||
return QRErrorCorrectLevel?.[normalized] ?? QRErrorCorrectLevel.M;
|
||||
};
|
||||
const normalized = String(level || 'M').toUpperCase()
|
||||
return QRErrorCorrectLevel?.[normalized] ?? QRErrorCorrectLevel.M
|
||||
}
|
||||
|
||||
export const generateQrMatrix = ({
|
||||
value,
|
||||
errorCorrectionLevel = "M",
|
||||
}) => {
|
||||
export const generateQrMatrix = ({ value, errorCorrectionLevel = 'M' }) => {
|
||||
if (!value) {
|
||||
throw new Error("generateQrMatrix requires a value");
|
||||
throw new Error('generateQrMatrix requires a value')
|
||||
}
|
||||
|
||||
const qr = new QRCode(-1, getErrorLevel(errorCorrectionLevel));
|
||||
qr.addData(value);
|
||||
qr.make();
|
||||
const qr = new QRCode(-1, getErrorLevel(errorCorrectionLevel))
|
||||
qr.addData(value)
|
||||
qr.make()
|
||||
|
||||
const size = qr.getModuleCount();
|
||||
const matrix = new Array(size);
|
||||
const size = qr.getModuleCount()
|
||||
const matrix = new Array(size)
|
||||
|
||||
for (let row = 0; row < size; row += 1) {
|
||||
matrix[row] = new Array(size);
|
||||
matrix[row] = new Array(size)
|
||||
for (let col = 0; col < size; col += 1) {
|
||||
matrix[row][col] = qr.isDark(row, col);
|
||||
matrix[row][col] = qr.isDark(row, col)
|
||||
}
|
||||
}
|
||||
|
||||
return { matrix, size };
|
||||
};
|
||||
|
||||
return { matrix, size }
|
||||
}
|
||||
|
||||
+25
-39
@@ -1,56 +1,42 @@
|
||||
import dayjs from 'dayjs';
|
||||
import isToday from 'dayjs/plugin/isToday';
|
||||
import isYesterday from 'dayjs/plugin/isYesterday';
|
||||
import updateLocale from 'dayjs/plugin/updateLocale';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import reactotron from 'reactotron-react-native';
|
||||
import dayjs from 'dayjs'
|
||||
import isToday from 'dayjs/plugin/isToday'
|
||||
import isYesterday from 'dayjs/plugin/isYesterday'
|
||||
import updateLocale from 'dayjs/plugin/updateLocale'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import reactotron from 'reactotron-react-native'
|
||||
|
||||
// Dayjs plugins
|
||||
dayjs.extend(isToday);
|
||||
dayjs.extend(isYesterday);
|
||||
dayjs.extend(updateLocale);
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(isToday)
|
||||
dayjs.extend(isYesterday)
|
||||
dayjs.extend(updateLocale)
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
/**
|
||||
* Return the distance between the given date and now in words.
|
||||
*/
|
||||
export const distanceToNow = value => {
|
||||
export const distanceToNow = (value) => {
|
||||
if (!dayjs(value).isValid()) {
|
||||
return 'Invalid Date';
|
||||
return 'Invalid Date'
|
||||
}
|
||||
return dayjs(value).fromNow();
|
||||
};
|
||||
return dayjs(value).fromNow()
|
||||
}
|
||||
|
||||
export const warn = reactotron.warn;
|
||||
export const warn = reactotron.warn
|
||||
|
||||
export function decode(t, e) {
|
||||
for (
|
||||
var n,
|
||||
o,
|
||||
u = 0,
|
||||
l = 0,
|
||||
r = 0,
|
||||
d = [],
|
||||
h = 0,
|
||||
i = 0,
|
||||
a = null,
|
||||
c = Math.pow(10, e || 5);
|
||||
var n, o, u = 0, l = 0, r = 0, d = [], h = 0, i = 0, a = null, c = Math.pow(10, e || 5);
|
||||
u < t.length;
|
||||
|
||||
) {
|
||||
(a = null), (h = 0), (i = 0);
|
||||
do (a = t.charCodeAt(u++) - 63), (i |= (31 & a) << h), (h += 5);
|
||||
while (a >= 32);
|
||||
(n = 1 & i ? ~(i >> 1) : i >> 1), (h = i = 0);
|
||||
do (a = t.charCodeAt(u++) - 63), (i |= (31 & a) << h), (h += 5);
|
||||
while (a >= 32);
|
||||
|
||||
(o = 1 & i ? ~(i >> 1) : i >> 1),
|
||||
(l += n),
|
||||
(r += o),
|
||||
d.push([l / c, r / c]);
|
||||
;((a = null), (h = 0), (i = 0))
|
||||
do ((a = t.charCodeAt(u++) - 63), (i |= (31 & a) << h), (h += 5))
|
||||
while (a >= 32)
|
||||
;((n = 1 & i ? ~(i >> 1) : i >> 1), (h = i = 0))
|
||||
do ((a = t.charCodeAt(u++) - 63), (i |= (31 & a) << h), (h += 5))
|
||||
while (a >= 32)
|
||||
;((o = 1 & i ? ~(i >> 1) : i >> 1), (l += n), (r += o), d.push([l / c, r / c]))
|
||||
}
|
||||
return d.map(function (t) {
|
||||
return {latitude: t[0], longitude: t[1]};
|
||||
});
|
||||
return { latitude: t[0], longitude: t[1] }
|
||||
})
|
||||
}
|
||||
|
||||
+43
-48
@@ -4,55 +4,51 @@ import {
|
||||
deleteField,
|
||||
projectsRef,
|
||||
serverTimestamp,
|
||||
} from "../config/firebase";
|
||||
import { ensureAuthenticated } from "./authRedirect";
|
||||
} from '../config/firebase'
|
||||
import { ensureAuthenticated } from './authRedirect'
|
||||
|
||||
export const LIKE_TARGET = {
|
||||
SONG: "song",
|
||||
PLAYBACK: "playback",
|
||||
};
|
||||
SONG: 'song',
|
||||
PLAYBACK: 'playback',
|
||||
}
|
||||
|
||||
export const getLikeFieldPath = (target = LIKE_TARGET.SONG) => {
|
||||
return target === LIKE_TARGET.PLAYBACK ? "likes.playback" : "likes.song";
|
||||
};
|
||||
return target === LIKE_TARGET.PLAYBACK ? 'likes.playback' : 'likes.song'
|
||||
}
|
||||
|
||||
const getLikeTimestampPath = (target = LIKE_TARGET.SONG) => {
|
||||
if (target === LIKE_TARGET.PLAYBACK) {
|
||||
return "likes.playbackLikedAt";
|
||||
return 'likes.playbackLikedAt'
|
||||
}
|
||||
if (target === LIKE_TARGET.SONG) {
|
||||
return "likes.songLikedAt";
|
||||
return 'likes.songLikedAt'
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return null
|
||||
}
|
||||
|
||||
export const getProjectLikes = (project, target = LIKE_TARGET.SONG) => {
|
||||
const path = target === LIKE_TARGET.PLAYBACK ? "playback" : "song";
|
||||
const likes = project?.likes;
|
||||
const list = likes ? likes[path] : null;
|
||||
const normalized = Array.isArray(list) ? list : [];
|
||||
const legacy = Array.isArray(project?.likedBy) ? project.likedBy : [];
|
||||
const path = target === LIKE_TARGET.PLAYBACK ? 'playback' : 'song'
|
||||
const likes = project?.likes
|
||||
const list = likes ? likes[path] : null
|
||||
const normalized = Array.isArray(list) ? list : []
|
||||
const legacy = Array.isArray(project?.likedBy) ? project.likedBy : []
|
||||
|
||||
if (target !== LIKE_TARGET.SONG || legacy.length === 0) {
|
||||
return normalized;
|
||||
return normalized
|
||||
}
|
||||
|
||||
const merged = new Set(normalized);
|
||||
const merged = new Set(normalized)
|
||||
legacy.forEach((uid) => {
|
||||
if (uid) merged.add(uid);
|
||||
});
|
||||
if (uid) merged.add(uid)
|
||||
})
|
||||
|
||||
return Array.from(merged);
|
||||
};
|
||||
return Array.from(merged)
|
||||
}
|
||||
|
||||
export const isProjectLikedByUser = (
|
||||
project,
|
||||
target,
|
||||
currentUID
|
||||
) => {
|
||||
if (!currentUID) return false;
|
||||
return getProjectLikes(project, target).includes(currentUID);
|
||||
};
|
||||
export const isProjectLikedByUser = (project, target, currentUID) => {
|
||||
if (!currentUID) return false
|
||||
return getProjectLikes(project, target).includes(currentUID)
|
||||
}
|
||||
|
||||
export const toggleProjectLike = async ({
|
||||
projectId,
|
||||
@@ -60,39 +56,38 @@ export const toggleProjectLike = async ({
|
||||
currentUID,
|
||||
next,
|
||||
}) => {
|
||||
if (!projectId) return;
|
||||
if (!ensureAuthenticated(currentUID)) return;
|
||||
const likeFieldKey =
|
||||
target === LIKE_TARGET.PLAYBACK ? "playback" : "song";
|
||||
const likeOperation = next ? arrayUnion(currentUID) : arrayRemove(currentUID);
|
||||
if (!projectId) return
|
||||
if (!ensureAuthenticated(currentUID)) return
|
||||
const likeFieldKey = target === LIKE_TARGET.PLAYBACK ? 'playback' : 'song'
|
||||
const likeOperation = next ? arrayUnion(currentUID) : arrayRemove(currentUID)
|
||||
const likedAtKey =
|
||||
target === LIKE_TARGET.PLAYBACK
|
||||
? "playbackLikedAt"
|
||||
? 'playbackLikedAt'
|
||||
: target === LIKE_TARGET.SONG
|
||||
? "songLikedAt"
|
||||
: null;
|
||||
? 'songLikedAt'
|
||||
: null
|
||||
const likesPayload = {
|
||||
likes: {
|
||||
[likeFieldKey]: likeOperation,
|
||||
...(likedAtKey && currentUID
|
||||
? {
|
||||
[likedAtKey]: {
|
||||
[currentUID]: next ? serverTimestamp() : deleteField(),
|
||||
},
|
||||
}
|
||||
[likedAtKey]: {
|
||||
[currentUID]: next ? serverTimestamp() : deleteField(),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
await projectsRef.doc(projectId).set(
|
||||
{
|
||||
...(target === LIKE_TARGET.SONG
|
||||
? {
|
||||
// Keep legacy likedBy in sync for clients still reading this field
|
||||
likedBy: next ? arrayUnion(currentUID) : arrayRemove(currentUID),
|
||||
}
|
||||
// Keep legacy likedBy in sync for clients still reading this field
|
||||
likedBy: next ? arrayUnion(currentUID) : arrayRemove(currentUID),
|
||||
}
|
||||
: {}),
|
||||
...likesPayload,
|
||||
},
|
||||
{ merge: true }
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const normalize = (s = "") =>
|
||||
export const normalize = (s = '') =>
|
||||
s
|
||||
.normalize("NFD")
|
||||
.replace(/\p{Diacritic}/gu, "")
|
||||
.normalize('NFD')
|
||||
.replace(/\p{Diacritic}/gu, '')
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
|
||||
+148
-174
@@ -1,70 +1,56 @@
|
||||
import { Routes } from "../navigation/Routes";
|
||||
import { Routes } from '../navigation/Routes'
|
||||
|
||||
export const CREATION_STAGE_KEYS = [
|
||||
"songwriter",
|
||||
"beatmaker",
|
||||
"director",
|
||||
"publisher",
|
||||
];
|
||||
export const CREATION_STAGE_KEYS = ['songwriter', 'beatmaker', 'director', 'publisher']
|
||||
|
||||
const getSectionLyrics = (section) => {
|
||||
if (typeof section === "string") {
|
||||
return section.trim();
|
||||
if (typeof section === 'string') {
|
||||
return section.trim()
|
||||
}
|
||||
if (section && typeof section === "object") {
|
||||
const value =
|
||||
typeof section.lyrics === "string" ? section.lyrics.trim() : "";
|
||||
return value;
|
||||
if (section && typeof section === 'object') {
|
||||
const value = typeof section.lyrics === 'string' ? section.lyrics.trim() : ''
|
||||
return value
|
||||
}
|
||||
return "";
|
||||
};
|
||||
return ''
|
||||
}
|
||||
|
||||
const getLyricsCount = (project) => {
|
||||
if (!project) return 0;
|
||||
const { lyrics } = project;
|
||||
if (!project) return 0
|
||||
const { lyrics } = project
|
||||
if (Array.isArray(lyrics)) {
|
||||
return lyrics.reduce(
|
||||
(count, section) => (getSectionLyrics(section) ? count + 1 : count),
|
||||
0,
|
||||
);
|
||||
return lyrics.reduce((count, section) => (getSectionLyrics(section) ? count + 1 : count), 0)
|
||||
}
|
||||
if (lyrics && typeof lyrics === "object") {
|
||||
if (lyrics && typeof lyrics === 'object') {
|
||||
return Object.values(lyrics).reduce(
|
||||
(count, value) => (getSectionLyrics(value) ? count + 1 : count),
|
||||
0,
|
||||
);
|
||||
0
|
||||
)
|
||||
}
|
||||
return getSectionLyrics(lyrics) ? 1 : 0;
|
||||
};
|
||||
return getSectionLyrics(lyrics) ? 1 : 0
|
||||
}
|
||||
|
||||
const getStageMetadata = (project) => {
|
||||
const lyricsCount = getLyricsCount(project);
|
||||
const hasLyrics = !!(project?.hasLyrics || lyricsCount > 0);
|
||||
const hasSongUrl = !!project?.songUrl;
|
||||
const cover = project?.cover || {};
|
||||
const hasCoverUrl = !!project?.coverUrl;
|
||||
const hasPlaybackAsset = !!project?.playbackUrl;
|
||||
const hasPlayback = hasPlaybackAsset || hasSongUrl;
|
||||
const youtubeUrl = project?.youtubeUrl ?? null;
|
||||
const youtubeStatus = project?.youtubeStatus ?? null;
|
||||
const youtubeError = project?.youtubeError ?? null;
|
||||
const hasYoutubePublication = !!youtubeUrl;
|
||||
const isYoutubePublishing = [
|
||||
"PUBLISHING",
|
||||
"UPLOADING",
|
||||
"PROCESSING",
|
||||
"QUEUED",
|
||||
].includes(youtubeStatus);
|
||||
const musicUrls = Array.isArray(project?.musicUrls)
|
||||
? project.musicUrls.filter(Boolean)
|
||||
: [];
|
||||
const musicStatus = project?.musicStatus || null;
|
||||
const coverStatus = project?.coverStatus || null;
|
||||
const playbackStatus = project?.playbackStatus || null;
|
||||
const lyricsCount = getLyricsCount(project)
|
||||
const hasLyrics = !!(project?.hasLyrics || lyricsCount > 0)
|
||||
const hasSongUrl = !!project?.songUrl
|
||||
const cover = project?.cover || {}
|
||||
const hasCoverUrl = !!project?.coverUrl
|
||||
const hasPlaybackAsset = !!project?.playbackUrl
|
||||
const hasPlayback = hasPlaybackAsset || hasSongUrl
|
||||
const youtubeUrl = project?.youtubeUrl ?? null
|
||||
const youtubeStatus = project?.youtubeStatus ?? null
|
||||
const youtubeError = project?.youtubeError ?? null
|
||||
const hasYoutubePublication = !!youtubeUrl
|
||||
const isYoutubePublishing = ['PUBLISHING', 'UPLOADING', 'PROCESSING', 'QUEUED'].includes(
|
||||
youtubeStatus
|
||||
)
|
||||
const musicUrls = Array.isArray(project?.musicUrls) ? project.musicUrls.filter(Boolean) : []
|
||||
const musicStatus = project?.musicStatus || null
|
||||
const coverStatus = project?.coverStatus || null
|
||||
const playbackStatus = project?.playbackStatus || null
|
||||
const isPlaybackGenerating =
|
||||
project?.playbackGenerating === true ||
|
||||
project?.isPlaybackGenerating === true ||
|
||||
playbackStatus === "GENERATING";
|
||||
playbackStatus === 'GENERATING'
|
||||
|
||||
return {
|
||||
lyricsCount,
|
||||
@@ -79,34 +65,34 @@ const getStageMetadata = (project) => {
|
||||
playbackStatus,
|
||||
youtubeStatus,
|
||||
youtubeError,
|
||||
isMusicGenerating: musicStatus === "GENERATING",
|
||||
isCoverGenerating: coverStatus === "GENERATING",
|
||||
isMusicGenerating: musicStatus === 'GENERATING',
|
||||
isCoverGenerating: coverStatus === 'GENERATING',
|
||||
isPlaybackGenerating,
|
||||
hasYoutubePublication,
|
||||
isYoutubePublishing,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const getStageLockState = (key, metadata) => {
|
||||
switch (key) {
|
||||
case "songwriter":
|
||||
return metadata.hasSongUrl;
|
||||
case "beatmaker":
|
||||
case 'songwriter':
|
||||
return metadata.hasSongUrl
|
||||
case 'beatmaker':
|
||||
if (metadata.hasCover) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
return metadata.lyricsCount <= 0;
|
||||
case "director":
|
||||
return metadata.lyricsCount <= 0
|
||||
case 'director':
|
||||
if (metadata.hasPlaybackAsset) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
return !metadata.hasCover;
|
||||
case "publisher":
|
||||
return !metadata.hasPlaybackAsset;
|
||||
return !metadata.hasCover
|
||||
case 'publisher':
|
||||
return !metadata.hasPlaybackAsset
|
||||
default:
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const getStageDescription = (key, metadata) => {
|
||||
const {
|
||||
@@ -117,220 +103,208 @@ const getStageDescription = (key, metadata) => {
|
||||
hasMusicDraft,
|
||||
isMusicGenerating,
|
||||
isPlaybackGenerating,
|
||||
} = metadata;
|
||||
} = metadata
|
||||
|
||||
switch (key) {
|
||||
case "songwriter":
|
||||
case 'songwriter':
|
||||
if (!hasLyrics) {
|
||||
return "Commencer à créer des paroles pour votre chanson";
|
||||
return 'Commencer à créer des paroles pour votre chanson'
|
||||
}
|
||||
if (!hasSongUrl) {
|
||||
return "Continuer la création de paroles";
|
||||
return 'Continuer la création de paroles'
|
||||
}
|
||||
return "Modifier les paroles créées";
|
||||
case "beatmaker":
|
||||
return 'Modifier les paroles créées'
|
||||
case 'beatmaker':
|
||||
if (metadata.hasCover) {
|
||||
return "Impossible de modifier la cover ou la production musicale";
|
||||
return 'Impossible de modifier la cover ou la production musicale'
|
||||
}
|
||||
if (!hasLyrics) {
|
||||
return "Écrivez vos paroles pour débloquer la musique";
|
||||
return 'Écrivez vos paroles pour débloquer la musique'
|
||||
}
|
||||
if (isMusicGenerating) {
|
||||
return "Votre morceau est en cours de création";
|
||||
return 'Votre morceau est en cours de création'
|
||||
}
|
||||
if (hasSongUrl) {
|
||||
if (!hasCover) {
|
||||
return "Créer ou modifier la cover de votre morceau";
|
||||
return 'Créer ou modifier la cover de votre morceau'
|
||||
}
|
||||
return "Modifier la cover ou la production musicale";
|
||||
return 'Modifier la cover ou la production musicale'
|
||||
}
|
||||
if (hasMusicDraft) {
|
||||
return "Modifier la production musicale";
|
||||
return 'Modifier la production musicale'
|
||||
}
|
||||
return "Commencer la création de votre morceau";
|
||||
case "director":
|
||||
return 'Commencer la création de votre morceau'
|
||||
case 'director':
|
||||
if (!hasCover) {
|
||||
return "Générez une cover pour débloquer le playback";
|
||||
return 'Générez une cover pour débloquer le playback'
|
||||
}
|
||||
if (isPlaybackGenerating) {
|
||||
return "Votre playback est en cours de préparation";
|
||||
return 'Votre playback est en cours de préparation'
|
||||
}
|
||||
if (hasPlaybackAsset) {
|
||||
return "Modifier le playback généré";
|
||||
return 'Modifier le playback généré'
|
||||
}
|
||||
return "Commencer la création de votre playback";
|
||||
case "publisher":
|
||||
return 'Commencer la création de votre playback'
|
||||
case 'publisher':
|
||||
if (!hasPlaybackAsset) {
|
||||
return "Créez un playback pour débloquer la publication";
|
||||
return 'Créez un playback pour débloquer la publication'
|
||||
}
|
||||
if (metadata.isYoutubePublishing) {
|
||||
return "Publication de votre vidéo en cours";
|
||||
return 'Publication de votre vidéo en cours'
|
||||
}
|
||||
if (metadata.youtubeError) {
|
||||
return "La publication a échoué, réessayez.";
|
||||
return 'La publication a échoué, réessayez.'
|
||||
}
|
||||
if (metadata.hasYoutubePublication) {
|
||||
return "Votre vidéo est en ligne et prête à être partagée";
|
||||
return 'Votre vidéo est en ligne et prête à être partagée'
|
||||
}
|
||||
return "Publier votre vidéo sur la chaîne YouTube MusicLand";
|
||||
return 'Publier votre vidéo sur la chaîne YouTube MusicLand'
|
||||
default:
|
||||
return "";
|
||||
return ''
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const getStageLockedDescription = (key, metadata) => {
|
||||
switch (key) {
|
||||
case "songwriter":
|
||||
return metadata.hasSongUrl
|
||||
? "Impossible de modifier les paroles"
|
||||
: undefined;
|
||||
case "beatmaker":
|
||||
case 'songwriter':
|
||||
return metadata.hasSongUrl ? 'Impossible de modifier les paroles' : undefined
|
||||
case 'beatmaker':
|
||||
if (metadata.hasCover) {
|
||||
return "Impossible de modifier la cover ou la production musicale";
|
||||
return 'Impossible de modifier la cover ou la production musicale'
|
||||
}
|
||||
return metadata.lyricsCount > 0
|
||||
? undefined
|
||||
: "Créez vos paroles pour débloquer le studio";
|
||||
case "director":
|
||||
return metadata.hasPlaybackAsset
|
||||
? "Impossible de modifier le playback"
|
||||
: undefined;
|
||||
case "publisher":
|
||||
return metadata.lyricsCount > 0 ? undefined : 'Créez vos paroles pour débloquer le studio'
|
||||
case 'director':
|
||||
return metadata.hasPlaybackAsset ? 'Impossible de modifier le playback' : undefined
|
||||
case 'publisher':
|
||||
if (!metadata.hasPlaybackAsset) {
|
||||
return "Générez un playback pour débloquer la publication";
|
||||
return 'Générez un playback pour débloquer la publication'
|
||||
}
|
||||
return metadata.hasYoutubePublication
|
||||
? "La vidéo est déjà publiée"
|
||||
: undefined;
|
||||
return metadata.hasYoutubePublication ? 'La vidéo est déjà publiée' : undefined
|
||||
default:
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const getStageCompletionState = (key, metadata) => {
|
||||
switch (key) {
|
||||
case "songwriter":
|
||||
return metadata.hasLyrics;
|
||||
case "beatmaker":
|
||||
return metadata.hasSongUrl || metadata.musicStatus === "GENERATED";
|
||||
case "director":
|
||||
return metadata.hasPlaybackAsset;
|
||||
case "publisher":
|
||||
return metadata.hasYoutubePublication;
|
||||
case 'songwriter':
|
||||
return metadata.hasLyrics
|
||||
case 'beatmaker':
|
||||
return metadata.hasSongUrl || metadata.musicStatus === 'GENERATED'
|
||||
case 'director':
|
||||
return metadata.hasPlaybackAsset
|
||||
case 'publisher':
|
||||
return metadata.hasYoutubePublication
|
||||
default:
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const getCreationStageStates = (project) => {
|
||||
const metadata = getStageMetadata(project);
|
||||
const metadata = getStageMetadata(project)
|
||||
return CREATION_STAGE_KEYS.map((key, index) => {
|
||||
const isLocked = getStageLockState(key, metadata);
|
||||
const description = getStageDescription(key, metadata);
|
||||
const lockedDescription = getStageLockedDescription(key, metadata);
|
||||
const isLocked = getStageLockState(key, metadata)
|
||||
const description = getStageDescription(key, metadata)
|
||||
const lockedDescription = getStageLockedDescription(key, metadata)
|
||||
|
||||
return {
|
||||
key,
|
||||
index,
|
||||
isLocked,
|
||||
isCompleted: getStageCompletionState(key, metadata),
|
||||
description:
|
||||
isLocked && lockedDescription ? lockedDescription : description,
|
||||
description: isLocked && lockedDescription ? lockedDescription : description,
|
||||
lockedDescription,
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const getStageAction = (key, project) => {
|
||||
switch (key) {
|
||||
case "songwriter":
|
||||
case 'songwriter':
|
||||
return {
|
||||
route:
|
||||
getLyricsCount(project) > 0 ? Routes.Lyrics : Routes.WritingLyrics,
|
||||
};
|
||||
case "beatmaker": {
|
||||
route: getLyricsCount(project) > 0 ? Routes.Lyrics : Routes.WritingLyrics,
|
||||
}
|
||||
case 'beatmaker': {
|
||||
if (!project) {
|
||||
return { route: Routes.Compose };
|
||||
return { route: Routes.Compose }
|
||||
}
|
||||
|
||||
const musicStatus = project?.musicStatus || null;
|
||||
const songUrl = project?.songUrl || null;
|
||||
const coverStatus = project?.coverStatus || null;
|
||||
const cover = project?.cover || {};
|
||||
const hasCoverBackground = !!cover?.generatedBackground;
|
||||
const hasCoverResult = !!cover?.result;
|
||||
const hasFinalCover = !!project?.coverUrl;
|
||||
const musicStatus = project?.musicStatus || null
|
||||
const songUrl = project?.songUrl || null
|
||||
const coverStatus = project?.coverStatus || null
|
||||
const cover = project?.cover || {}
|
||||
const hasCoverBackground = !!cover?.generatedBackground
|
||||
const hasCoverResult = !!cover?.result
|
||||
const hasFinalCover = !!project?.coverUrl
|
||||
|
||||
if (musicStatus === "GENERATING") {
|
||||
return { route: Routes.GeneratingSong };
|
||||
if (musicStatus === 'GENERATING') {
|
||||
return { route: Routes.GeneratingSong }
|
||||
}
|
||||
|
||||
if (!songUrl) {
|
||||
if (musicStatus === "GENERATED") {
|
||||
return { route: Routes.SongReady };
|
||||
if (musicStatus === 'GENERATED') {
|
||||
return { route: Routes.SongReady }
|
||||
}
|
||||
return { route: Routes.Compose };
|
||||
return { route: Routes.Compose }
|
||||
}
|
||||
|
||||
if (coverStatus === "GENERATING") {
|
||||
return { route: Routes.PouchReady };
|
||||
if (coverStatus === 'GENERATING') {
|
||||
return { route: Routes.PouchReady }
|
||||
}
|
||||
|
||||
if (!hasCoverBackground) {
|
||||
return { route: Routes.ChooseCoverType };
|
||||
return { route: Routes.ChooseCoverType }
|
||||
}
|
||||
|
||||
if (!hasCoverResult) {
|
||||
return { route: Routes.PouchReady };
|
||||
return { route: Routes.PouchReady }
|
||||
}
|
||||
|
||||
if (!hasFinalCover) {
|
||||
return { route: Routes.ValidateCover };
|
||||
return { route: Routes.ValidateCover }
|
||||
}
|
||||
|
||||
return { route: Routes.ChooseCoverType };
|
||||
return { route: Routes.ChooseCoverType }
|
||||
}
|
||||
case "director":
|
||||
case 'director':
|
||||
return {
|
||||
route: Routes.Playback,
|
||||
params: project ? { project } : undefined,
|
||||
};
|
||||
case "publisher": {
|
||||
}
|
||||
case 'publisher': {
|
||||
if (!project?.id) {
|
||||
return { route: Routes.PublishYoutube };
|
||||
return { route: Routes.PublishYoutube }
|
||||
}
|
||||
return {
|
||||
route: Routes.PublishYoutube,
|
||||
params: { projectId: project.id },
|
||||
};
|
||||
}
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const findFirstUnlockedStageIndex = (project) => {
|
||||
const stages = getCreationStageStates(project);
|
||||
const stages = getCreationStageStates(project)
|
||||
if (!stages.length) {
|
||||
return 0;
|
||||
return 0
|
||||
}
|
||||
|
||||
const actionableIndex = stages.findIndex(
|
||||
(stage) => !stage.isCompleted && !stage.isLocked
|
||||
);
|
||||
const actionableIndex = stages.findIndex((stage) => !stage.isCompleted && !stage.isLocked)
|
||||
if (actionableIndex !== -1) {
|
||||
return actionableIndex;
|
||||
return actionableIndex
|
||||
}
|
||||
|
||||
const firstIncomplete = stages.findIndex((stage) => !stage.isCompleted);
|
||||
const firstIncomplete = stages.findIndex((stage) => !stage.isCompleted)
|
||||
if (firstIncomplete !== -1) {
|
||||
return firstIncomplete;
|
||||
return firstIncomplete
|
||||
}
|
||||
|
||||
const firstUnlocked = stages.findIndex((stage) => !stage.isLocked);
|
||||
const firstUnlocked = stages.findIndex((stage) => !stage.isLocked)
|
||||
if (firstUnlocked !== -1) {
|
||||
return firstUnlocked;
|
||||
return firstUnlocked
|
||||
}
|
||||
|
||||
return stages.length - 1;
|
||||
};
|
||||
return stages.length - 1
|
||||
}
|
||||
|
||||
+82
-96
@@ -1,187 +1,173 @@
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { SheetManager } from 'react-native-actions-sheet'
|
||||
|
||||
import appJson from "../../app.json";
|
||||
import {
|
||||
musiclandShareHeading,
|
||||
musiclandShareMessage,
|
||||
musiclandShareUrl,
|
||||
} from "../data";
|
||||
import appJson from '../../app.json'
|
||||
import { musiclandShareHeading, musiclandShareMessage, musiclandShareUrl } from '../data'
|
||||
|
||||
const APP_SCHEME = appJson?.expo?.scheme || "musicland";
|
||||
const APP_SCHEME_BASE = `${APP_SCHEME}://app`;
|
||||
const QR_REDIRECT_PATH = "link";
|
||||
const APP_SCHEME = appJson?.expo?.scheme || 'musicland'
|
||||
const APP_SCHEME_BASE = `${APP_SCHEME}://app`
|
||||
const QR_REDIRECT_PATH = 'link'
|
||||
|
||||
const trimTrailingSlash = (value) =>
|
||||
typeof value === "string" ? value.replace(/\/+$/, "") : "";
|
||||
const trimTrailingSlash = (value) => (typeof value === 'string' ? value.replace(/\/+$/, '') : '')
|
||||
|
||||
const trimLeadingSlash = (value) =>
|
||||
typeof value === "string" ? value.replace(/^\/+/, "") : "";
|
||||
const trimLeadingSlash = (value) => (typeof value === 'string' ? value.replace(/^\/+/, '') : '')
|
||||
|
||||
const buildQueryString = (params = {}) => {
|
||||
const entries = Object.entries(params).filter(
|
||||
([, value]) => value !== undefined && value !== null && value !== ""
|
||||
);
|
||||
([, value]) => value !== undefined && value !== null && value !== ''
|
||||
)
|
||||
|
||||
if (!entries.length) {
|
||||
return "";
|
||||
return ''
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
const searchParams = new URLSearchParams()
|
||||
entries.forEach(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => {
|
||||
searchParams.append(key, `${item}`);
|
||||
});
|
||||
searchParams.append(key, `${item}`)
|
||||
})
|
||||
} else {
|
||||
searchParams.append(key, `${value}`);
|
||||
searchParams.append(key, `${value}`)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const query = searchParams.toString();
|
||||
return query ? `?${query}` : "";
|
||||
};
|
||||
const query = searchParams.toString()
|
||||
return query ? `?${query}` : ''
|
||||
}
|
||||
|
||||
const createSchemeDeepLink = (path = "", params = {}) => {
|
||||
const sanitizedPath = trimLeadingSlash(path);
|
||||
const basePath = sanitizedPath
|
||||
? `${APP_SCHEME_BASE}/${sanitizedPath}`
|
||||
: APP_SCHEME_BASE;
|
||||
const createSchemeDeepLink = (path = '', params = {}) => {
|
||||
const sanitizedPath = trimLeadingSlash(path)
|
||||
const basePath = sanitizedPath ? `${APP_SCHEME_BASE}/${sanitizedPath}` : APP_SCHEME_BASE
|
||||
|
||||
return `${basePath}${buildQueryString(params)}`;
|
||||
};
|
||||
return `${basePath}${buildQueryString(params)}`
|
||||
}
|
||||
|
||||
const resolveBaseShareUrl = () => {
|
||||
if (typeof window !== "undefined" && window.location?.origin) {
|
||||
const origin = window.location.origin;
|
||||
if (typeof window !== 'undefined' && window.location?.origin) {
|
||||
const origin = window.location.origin
|
||||
if (/localhost|127\.0\.0\.1/i.test(origin)) {
|
||||
return origin;
|
||||
return origin
|
||||
}
|
||||
}
|
||||
return musiclandShareUrl;
|
||||
};
|
||||
return musiclandShareUrl
|
||||
}
|
||||
|
||||
const createShareQrUrl = ({ sharePath = "", fallbackUrl } = {}) => {
|
||||
const sanitizedPath = trimLeadingSlash(sharePath);
|
||||
const schemeDeepLink = createSchemeDeepLink(sanitizedPath);
|
||||
const baseUrl = trimTrailingSlash(resolveBaseShareUrl());
|
||||
const createShareQrUrl = ({ sharePath = '', fallbackUrl } = {}) => {
|
||||
const sanitizedPath = trimLeadingSlash(sharePath)
|
||||
const schemeDeepLink = createSchemeDeepLink(sanitizedPath)
|
||||
const baseUrl = trimTrailingSlash(resolveBaseShareUrl())
|
||||
|
||||
const resolvedFallback =
|
||||
fallbackUrl ||
|
||||
(baseUrl ? (sanitizedPath ? `${baseUrl}/${sanitizedPath}` : baseUrl) : "");
|
||||
fallbackUrl || (baseUrl ? (sanitizedPath ? `${baseUrl}/${sanitizedPath}` : baseUrl) : '')
|
||||
|
||||
if (!baseUrl) {
|
||||
return schemeDeepLink;
|
||||
return schemeDeepLink
|
||||
}
|
||||
|
||||
try {
|
||||
const redirectUrl = new URL(`${baseUrl}/${QR_REDIRECT_PATH}`);
|
||||
redirectUrl.searchParams.set("scheme", schemeDeepLink);
|
||||
const redirectUrl = new URL(`${baseUrl}/${QR_REDIRECT_PATH}`)
|
||||
redirectUrl.searchParams.set('scheme', schemeDeepLink)
|
||||
if (resolvedFallback) {
|
||||
redirectUrl.searchParams.set("fallback", resolvedFallback);
|
||||
redirectUrl.searchParams.set('fallback', resolvedFallback)
|
||||
}
|
||||
return redirectUrl.toString();
|
||||
return redirectUrl.toString()
|
||||
} catch (error) {
|
||||
console.error("share.qr.url.create.error", error);
|
||||
return schemeDeepLink;
|
||||
console.error('share.qr.url.create.error', error)
|
||||
return schemeDeepLink
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const defaultPayload = {
|
||||
heading: musiclandShareHeading,
|
||||
url: musiclandShareUrl,
|
||||
shareTitle: "MusicLand",
|
||||
shareTitle: 'MusicLand',
|
||||
shareMessage: musiclandShareMessage,
|
||||
qrUrl: createShareQrUrl({
|
||||
sharePath: "",
|
||||
sharePath: '',
|
||||
fallbackUrl: musiclandShareUrl,
|
||||
}),
|
||||
copyUrl: musiclandShareUrl,
|
||||
linkLabel: musiclandShareUrl,
|
||||
appLink: APP_SCHEME_BASE,
|
||||
};
|
||||
}
|
||||
|
||||
export const createMusicSharePayload = ({ projectId, title, artist } = {}) => {
|
||||
if (!projectId) return null;
|
||||
if (!projectId) return null
|
||||
|
||||
const baseUrl = trimTrailingSlash(resolveBaseShareUrl());
|
||||
if (!baseUrl) return null;
|
||||
const encodedProjectId = encodeURIComponent(projectId);
|
||||
const sharePath = `music/${encodedProjectId}`;
|
||||
const shareUrl = `${baseUrl}/${sharePath}`;
|
||||
const schemeDeepLink = createSchemeDeepLink(sharePath);
|
||||
const baseUrl = trimTrailingSlash(resolveBaseShareUrl())
|
||||
if (!baseUrl) return null
|
||||
const encodedProjectId = encodeURIComponent(projectId)
|
||||
const sharePath = `music/${encodedProjectId}`
|
||||
const shareUrl = `${baseUrl}/${sharePath}`
|
||||
const schemeDeepLink = createSchemeDeepLink(sharePath)
|
||||
const qrUrl = createShareQrUrl({
|
||||
sharePath,
|
||||
fallbackUrl: shareUrl,
|
||||
});
|
||||
})
|
||||
|
||||
let shareMessage = musiclandShareMessage;
|
||||
let shareMessage = musiclandShareMessage
|
||||
if (title) {
|
||||
shareMessage = `Découvre "${title}"`;
|
||||
shareMessage = `Découvre "${title}"`
|
||||
if (artist) {
|
||||
shareMessage += ` de ${artist}`;
|
||||
shareMessage += ` de ${artist}`
|
||||
}
|
||||
shareMessage += " sur MusicLand.";
|
||||
shareMessage += ' sur MusicLand.'
|
||||
}
|
||||
|
||||
return {
|
||||
heading: "Partager le morceau",
|
||||
shareTitle: title ? `${title} - MusicLand` : "MusicLand",
|
||||
heading: 'Partager le morceau',
|
||||
shareTitle: title ? `${title} - MusicLand` : 'MusicLand',
|
||||
shareMessage,
|
||||
url: shareUrl,
|
||||
qrUrl,
|
||||
copyUrl: shareUrl,
|
||||
linkLabel: shareUrl,
|
||||
appLink: schemeDeepLink,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const createPlaybackSharePayload = ({
|
||||
projectId,
|
||||
title,
|
||||
artist,
|
||||
playbackUrl,
|
||||
} = {}) => {
|
||||
if (!projectId) return null;
|
||||
export const createPlaybackSharePayload = ({ projectId, title, artist, playbackUrl } = {}) => {
|
||||
if (!projectId) return null
|
||||
|
||||
const baseUrl = trimTrailingSlash(resolveBaseShareUrl());
|
||||
if (!baseUrl) return null;
|
||||
const encodedProjectId = encodeURIComponent(projectId);
|
||||
const sharePath = `playback/${encodedProjectId}`;
|
||||
const shareUrl = `${baseUrl}/${sharePath}`;
|
||||
const schemeDeepLink = createSchemeDeepLink(sharePath);
|
||||
const baseUrl = trimTrailingSlash(resolveBaseShareUrl())
|
||||
if (!baseUrl) return null
|
||||
const encodedProjectId = encodeURIComponent(projectId)
|
||||
const sharePath = `playback/${encodedProjectId}`
|
||||
const shareUrl = `${baseUrl}/${sharePath}`
|
||||
const schemeDeepLink = createSchemeDeepLink(sharePath)
|
||||
const qrUrl = createShareQrUrl({
|
||||
sharePath,
|
||||
fallbackUrl: playbackUrl || shareUrl,
|
||||
});
|
||||
})
|
||||
|
||||
let shareMessage = "Découvre ce playback sur MusicLand.";
|
||||
let shareMessage = 'Découvre ce playback sur MusicLand.'
|
||||
if (title) {
|
||||
shareMessage = `Découvre "${title}"`;
|
||||
shareMessage = `Découvre "${title}"`
|
||||
if (artist) {
|
||||
shareMessage += ` par ${artist}`;
|
||||
shareMessage += ` par ${artist}`
|
||||
}
|
||||
shareMessage += " sur MusicLand.";
|
||||
shareMessage += ' sur MusicLand.'
|
||||
} else if (artist) {
|
||||
shareMessage = `Découvre ce playback de ${artist} sur MusicLand.`;
|
||||
shareMessage = `Découvre ce playback de ${artist} sur MusicLand.`
|
||||
}
|
||||
|
||||
return {
|
||||
heading: "Partager le playback",
|
||||
shareTitle: title ? `${title} - MusicLand` : "MusicLand",
|
||||
heading: 'Partager le playback',
|
||||
shareTitle: title ? `${title} - MusicLand` : 'MusicLand',
|
||||
shareMessage,
|
||||
url: shareUrl,
|
||||
qrUrl,
|
||||
copyUrl: shareUrl,
|
||||
linkLabel: shareUrl,
|
||||
appLink: schemeDeepLink,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const openShareSheet = (payload = {}) => {
|
||||
SheetManager.show("Share", {
|
||||
SheetManager.show('Share', {
|
||||
payload: {
|
||||
...defaultPayload,
|
||||
...payload,
|
||||
},
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
+182
-187
@@ -1,84 +1,84 @@
|
||||
const START_CASE_REGEX = /[_\-\s]+/g;
|
||||
const START_CASE_REGEX = /[_\-\s]+/g
|
||||
|
||||
const toStartCase = (value) => {
|
||||
const str = String(value || "")
|
||||
.replace(START_CASE_REGEX, " ")
|
||||
.trim();
|
||||
if (!str) return "";
|
||||
const str = String(value || '')
|
||||
.replace(START_CASE_REGEX, ' ')
|
||||
.trim()
|
||||
if (!str) return ''
|
||||
return str
|
||||
.split(" ")
|
||||
.split(' ')
|
||||
.filter(Boolean)
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
};
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
export const STRUCTURE_SEGMENTS = {
|
||||
couplet: {
|
||||
label: "Couplet",
|
||||
promptLabel: "Couplet",
|
||||
label: 'Couplet',
|
||||
promptLabel: 'Couplet',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: false,
|
||||
inputHeight: 225,
|
||||
requiresLyrics: true,
|
||||
aliases: ["vers", "verse", "couplet"],
|
||||
aliases: ['vers', 'verse', 'couplet'],
|
||||
},
|
||||
refrain: {
|
||||
label: "Refrain",
|
||||
promptLabel: "Refrain",
|
||||
label: 'Refrain',
|
||||
promptLabel: 'Refrain',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: false,
|
||||
inputHeight: 170,
|
||||
requiresLyrics: true,
|
||||
aliases: ["chorus", "refrain"],
|
||||
aliases: ['chorus', 'refrain'],
|
||||
},
|
||||
short_intro: {
|
||||
label: "Introduction instrumentale courte",
|
||||
promptLabel: "Introduction instrumentale courte",
|
||||
label: 'Introduction instrumentale courte',
|
||||
promptLabel: 'Introduction instrumentale courte',
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 150,
|
||||
exclusiveGroup: "intro",
|
||||
exclusiveGroup: 'intro',
|
||||
requiresLyrics: false,
|
||||
aliases: [
|
||||
"introduction instrumentale courte",
|
||||
"intro courte",
|
||||
"intro instrumentale courte",
|
||||
"short intro",
|
||||
'introduction instrumentale courte',
|
||||
'intro courte',
|
||||
'intro instrumentale courte',
|
||||
'short intro',
|
||||
],
|
||||
},
|
||||
long_intro: {
|
||||
label: "Introduction instrumentale longue",
|
||||
promptLabel: "Introduction instrumentale longue",
|
||||
label: 'Introduction instrumentale longue',
|
||||
promptLabel: 'Introduction instrumentale longue',
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 150,
|
||||
exclusiveGroup: "intro",
|
||||
exclusiveGroup: 'intro',
|
||||
requiresLyrics: false,
|
||||
aliases: [
|
||||
"introduction instrumentale longue",
|
||||
"intro longue",
|
||||
"long intro",
|
||||
"intro instrumentale longue",
|
||||
'introduction instrumentale longue',
|
||||
'intro longue',
|
||||
'long intro',
|
||||
'intro instrumentale longue',
|
||||
],
|
||||
},
|
||||
pre_refrain_instrumental: {
|
||||
label: "Pré-refrain instrumental",
|
||||
promptLabel: "Pré-refrain instrumental",
|
||||
label: 'Pré-refrain instrumental',
|
||||
promptLabel: 'Pré-refrain instrumental',
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 150,
|
||||
exclusiveGroup: "pre_chorus",
|
||||
exclusiveGroup: 'pre_chorus',
|
||||
requiresLyrics: false,
|
||||
aliases: [],
|
||||
},
|
||||
pont: {
|
||||
label: "Pont",
|
||||
promptLabel: "Pont",
|
||||
label: 'Pont',
|
||||
promptLabel: 'Pont',
|
||||
showIndex: false,
|
||||
allowMultiple: true,
|
||||
optional: true,
|
||||
@@ -87,8 +87,8 @@ export const STRUCTURE_SEGMENTS = {
|
||||
aliases: [],
|
||||
},
|
||||
solo_de_guitare: {
|
||||
label: "Solo de guitare",
|
||||
promptLabel: "Solo de guitare",
|
||||
label: 'Solo de guitare',
|
||||
promptLabel: 'Solo de guitare',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: true,
|
||||
@@ -97,8 +97,8 @@ export const STRUCTURE_SEGMENTS = {
|
||||
aliases: [],
|
||||
},
|
||||
solo_de_guitare_electrique: {
|
||||
label: "Solo de guitare électrique",
|
||||
promptLabel: "Solo de guitare électrique",
|
||||
label: 'Solo de guitare électrique',
|
||||
promptLabel: 'Solo de guitare électrique',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: true,
|
||||
@@ -107,8 +107,8 @@ export const STRUCTURE_SEGMENTS = {
|
||||
aliases: [],
|
||||
},
|
||||
solo_de_batterie: {
|
||||
label: "Solo de batterie",
|
||||
promptLabel: "Solo de batterie",
|
||||
label: 'Solo de batterie',
|
||||
promptLabel: 'Solo de batterie',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: true,
|
||||
@@ -117,8 +117,8 @@ export const STRUCTURE_SEGMENTS = {
|
||||
aliases: [],
|
||||
},
|
||||
solo_de_saxophone: {
|
||||
label: "Solo de saxophone",
|
||||
promptLabel: "Solo de saxophone",
|
||||
label: 'Solo de saxophone',
|
||||
promptLabel: 'Solo de saxophone',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: true,
|
||||
@@ -127,8 +127,8 @@ export const STRUCTURE_SEGMENTS = {
|
||||
aliases: [],
|
||||
},
|
||||
solo_de_violon: {
|
||||
label: "Solo de violon",
|
||||
promptLabel: "Solo de violon",
|
||||
label: 'Solo de violon',
|
||||
promptLabel: 'Solo de violon',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: true,
|
||||
@@ -137,8 +137,8 @@ export const STRUCTURE_SEGMENTS = {
|
||||
aliases: [],
|
||||
},
|
||||
break: {
|
||||
label: "Break",
|
||||
promptLabel: "Break",
|
||||
label: 'Break',
|
||||
promptLabel: 'Break',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: true,
|
||||
@@ -147,8 +147,8 @@ export const STRUCTURE_SEGMENTS = {
|
||||
aliases: [],
|
||||
},
|
||||
interlude: {
|
||||
label: "Interlude",
|
||||
promptLabel: "Interlude",
|
||||
label: 'Interlude',
|
||||
promptLabel: 'Interlude',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: true,
|
||||
@@ -157,8 +157,8 @@ export const STRUCTURE_SEGMENTS = {
|
||||
aliases: [],
|
||||
},
|
||||
interlude_melodique: {
|
||||
label: "Interlude mélodique",
|
||||
promptLabel: "Interlude mélodique",
|
||||
label: 'Interlude mélodique',
|
||||
promptLabel: 'Interlude mélodique',
|
||||
showIndex: true,
|
||||
allowMultiple: true,
|
||||
optional: true,
|
||||
@@ -167,240 +167,235 @@ export const STRUCTURE_SEGMENTS = {
|
||||
aliases: [],
|
||||
},
|
||||
final_apogee: {
|
||||
label: "Final apogée",
|
||||
promptLabel: "Final apogée",
|
||||
label: 'Final apogée',
|
||||
promptLabel: 'Final apogée',
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 170,
|
||||
exclusiveGroup: "outro",
|
||||
exclusiveGroup: 'outro',
|
||||
requiresLyrics: false,
|
||||
aliases: [],
|
||||
},
|
||||
arret_net: {
|
||||
label: "Arrêt net",
|
||||
promptLabel: "Arrêt net",
|
||||
label: 'Arrêt net',
|
||||
promptLabel: 'Arrêt net',
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 150,
|
||||
exclusiveGroup: "outro",
|
||||
exclusiveGroup: 'outro',
|
||||
requiresLyrics: false,
|
||||
aliases: [],
|
||||
},
|
||||
fade_out: {
|
||||
label: "Fade out",
|
||||
promptLabel: "Fade out",
|
||||
label: 'Fade out',
|
||||
promptLabel: 'Fade out',
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 150,
|
||||
exclusiveGroup: "outro",
|
||||
exclusiveGroup: 'outro',
|
||||
requiresLyrics: false,
|
||||
aliases: [],
|
||||
},
|
||||
transition_douce: {
|
||||
label: "Transition douce vers le silence",
|
||||
promptLabel: "Transition douce",
|
||||
label: 'Transition douce vers le silence',
|
||||
promptLabel: 'Transition douce',
|
||||
showIndex: false,
|
||||
allowMultiple: false,
|
||||
optional: true,
|
||||
inputHeight: 150,
|
||||
exclusiveGroup: "outro",
|
||||
exclusiveGroup: 'outro',
|
||||
requiresLyrics: false,
|
||||
aliases: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const ALIAS_TO_KEY = Object.entries(STRUCTURE_SEGMENTS).reduce(
|
||||
(acc, [key, meta]) => {
|
||||
const aliases = Array.isArray(meta.aliases) ? meta.aliases : [];
|
||||
aliases.forEach((alias) => {
|
||||
acc[String(alias).toLowerCase()] = key;
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
const ALIAS_TO_KEY = Object.entries(STRUCTURE_SEGMENTS).reduce((acc, [key, meta]) => {
|
||||
const aliases = Array.isArray(meta.aliases) ? meta.aliases : []
|
||||
aliases.forEach((alias) => {
|
||||
acc[String(alias).toLowerCase()] = key
|
||||
})
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
const LEGACY_STRUCTURE_ALIASES = {
|
||||
pre_chorus: "pre_refrain_instrumental",
|
||||
"pré-refrain": "pre_refrain_instrumental",
|
||||
"pre-refrain": "pre_refrain_instrumental",
|
||||
"pre_refrain": "pre_refrain_instrumental",
|
||||
prechorus: "pre_refrain_instrumental",
|
||||
"pre chorus": "pre_refrain_instrumental",
|
||||
"pre-chorus": "pre_refrain_instrumental",
|
||||
instrumental_pre_chorus: "pre_refrain_instrumental",
|
||||
bridge: "pont",
|
||||
guitar_solo: "solo_de_guitare",
|
||||
electric_guitar_solo: "solo_de_guitare_electrique",
|
||||
drum_solo: "solo_de_batterie",
|
||||
sax_solo: "solo_de_saxophone",
|
||||
violin_solo: "solo_de_violon",
|
||||
melodic_interlude: "interlude_melodique",
|
||||
grand_finale: "final_apogee",
|
||||
sudden_stop: "arret_net",
|
||||
soft_transition: "transition_douce",
|
||||
};
|
||||
pre_chorus: 'pre_refrain_instrumental',
|
||||
'pré-refrain': 'pre_refrain_instrumental',
|
||||
'pre-refrain': 'pre_refrain_instrumental',
|
||||
pre_refrain: 'pre_refrain_instrumental',
|
||||
prechorus: 'pre_refrain_instrumental',
|
||||
'pre chorus': 'pre_refrain_instrumental',
|
||||
'pre-chorus': 'pre_refrain_instrumental',
|
||||
instrumental_pre_chorus: 'pre_refrain_instrumental',
|
||||
bridge: 'pont',
|
||||
guitar_solo: 'solo_de_guitare',
|
||||
electric_guitar_solo: 'solo_de_guitare_electrique',
|
||||
drum_solo: 'solo_de_batterie',
|
||||
sax_solo: 'solo_de_saxophone',
|
||||
violin_solo: 'solo_de_violon',
|
||||
melodic_interlude: 'interlude_melodique',
|
||||
grand_finale: 'final_apogee',
|
||||
sudden_stop: 'arret_net',
|
||||
soft_transition: 'transition_douce',
|
||||
}
|
||||
|
||||
Object.entries(LEGACY_STRUCTURE_ALIASES).forEach(([alias, target]) => {
|
||||
const key = String(alias).toLowerCase();
|
||||
ALIAS_TO_KEY[key] = target;
|
||||
});
|
||||
const key = String(alias).toLowerCase()
|
||||
ALIAS_TO_KEY[key] = target
|
||||
})
|
||||
|
||||
const sanitizeToken = (value) =>
|
||||
String(value || "")
|
||||
String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "_")
|
||||
.replace(/^_+|_+$/g, "");
|
||||
.replace(/[^a-z0-9]+/g, '_')
|
||||
.replace(/^_+|_+$/g, '')
|
||||
|
||||
export const getSegmentMeta = (type) => {
|
||||
const key = String(type || "").toLowerCase();
|
||||
return STRUCTURE_SEGMENTS[key] || null;
|
||||
};
|
||||
const key = String(type || '').toLowerCase()
|
||||
return STRUCTURE_SEGMENTS[key] || null
|
||||
}
|
||||
|
||||
export const segmentRequiresLyrics = (type) => {
|
||||
const meta = getSegmentMeta(type);
|
||||
if (!meta) return true;
|
||||
return meta.requiresLyrics !== false;
|
||||
};
|
||||
const meta = getSegmentMeta(type)
|
||||
if (!meta) return true
|
||||
return meta.requiresLyrics !== false
|
||||
}
|
||||
|
||||
export const OPTIONAL_STRUCTURE_SEGMENTS = Object.keys(
|
||||
STRUCTURE_SEGMENTS
|
||||
).filter((key) => STRUCTURE_SEGMENTS[key]?.optional);
|
||||
export const OPTIONAL_STRUCTURE_SEGMENTS = Object.keys(STRUCTURE_SEGMENTS).filter(
|
||||
(key) => STRUCTURE_SEGMENTS[key]?.optional
|
||||
)
|
||||
|
||||
export const normalizeStructureType = (value) => {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
const lower = raw.toLowerCase();
|
||||
if (STRUCTURE_SEGMENTS[lower]) return lower;
|
||||
if (ALIAS_TO_KEY[lower]) return ALIAS_TO_KEY[lower];
|
||||
const raw = String(value || '').trim()
|
||||
if (!raw) return ''
|
||||
const lower = raw.toLowerCase()
|
||||
if (STRUCTURE_SEGMENTS[lower]) return lower
|
||||
if (ALIAS_TO_KEY[lower]) return ALIAS_TO_KEY[lower]
|
||||
|
||||
const sanitized = sanitizeToken(raw);
|
||||
if (STRUCTURE_SEGMENTS[sanitized]) return sanitized;
|
||||
if (ALIAS_TO_KEY[sanitized]) return ALIAS_TO_KEY[sanitized];
|
||||
return sanitized;
|
||||
};
|
||||
const sanitized = sanitizeToken(raw)
|
||||
if (STRUCTURE_SEGMENTS[sanitized]) return sanitized
|
||||
if (ALIAS_TO_KEY[sanitized]) return ALIAS_TO_KEY[sanitized]
|
||||
return sanitized
|
||||
}
|
||||
|
||||
export const sanitizeStructureList = (structure = [], options = {}) => {
|
||||
if (!Array.isArray(structure)) return [];
|
||||
const { autoInjectPreChorus = true } = options || {};
|
||||
const output = [];
|
||||
let shouldInjectInstrumentalPreChorus = false;
|
||||
if (!Array.isArray(structure)) return []
|
||||
const { autoInjectPreChorus = true } = options || {}
|
||||
const output = []
|
||||
let shouldInjectInstrumentalPreChorus = false
|
||||
|
||||
structure.forEach((segment) => {
|
||||
const type = normalizeStructureType(segment);
|
||||
if (!type) return;
|
||||
const meta = getSegmentMeta(type);
|
||||
const type = normalizeStructureType(segment)
|
||||
if (!type) return
|
||||
const meta = getSegmentMeta(type)
|
||||
|
||||
if (meta) {
|
||||
if (meta.allowMultiple === false) {
|
||||
const alreadyIncluded = output.some((item) => item === type);
|
||||
if (alreadyIncluded) return;
|
||||
const alreadyIncluded = output.some((item) => item === type)
|
||||
if (alreadyIncluded) return
|
||||
}
|
||||
|
||||
if (meta.exclusiveGroup) {
|
||||
const previousIndex = output.findIndex((item) => {
|
||||
const existingMeta = getSegmentMeta(item);
|
||||
return existingMeta?.exclusiveGroup === meta.exclusiveGroup;
|
||||
});
|
||||
const existingMeta = getSegmentMeta(item)
|
||||
return existingMeta?.exclusiveGroup === meta.exclusiveGroup
|
||||
})
|
||||
if (previousIndex !== -1) {
|
||||
output.splice(previousIndex, 1);
|
||||
output.splice(previousIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "pre_refrain_instrumental" && autoInjectPreChorus) {
|
||||
shouldInjectInstrumentalPreChorus = true;
|
||||
return;
|
||||
if (type === 'pre_refrain_instrumental' && autoInjectPreChorus) {
|
||||
shouldInjectInstrumentalPreChorus = true
|
||||
return
|
||||
}
|
||||
|
||||
output.push(type);
|
||||
});
|
||||
output.push(type)
|
||||
})
|
||||
|
||||
let result = output;
|
||||
let result = output
|
||||
|
||||
if (autoInjectPreChorus && shouldInjectInstrumentalPreChorus) {
|
||||
const expanded = [];
|
||||
let hasRefrain = false;
|
||||
const expanded = []
|
||||
let hasRefrain = false
|
||||
|
||||
result.forEach((item) => {
|
||||
if (item === "refrain") {
|
||||
hasRefrain = true;
|
||||
const previous = expanded[expanded.length - 1];
|
||||
if (previous !== "pre_refrain_instrumental") {
|
||||
expanded.push("pre_refrain_instrumental");
|
||||
if (item === 'refrain') {
|
||||
hasRefrain = true
|
||||
const previous = expanded[expanded.length - 1]
|
||||
if (previous !== 'pre_refrain_instrumental') {
|
||||
expanded.push('pre_refrain_instrumental')
|
||||
}
|
||||
}
|
||||
expanded.push(item);
|
||||
});
|
||||
expanded.push(item)
|
||||
})
|
||||
|
||||
if (!hasRefrain) {
|
||||
expanded.push("pre_refrain_instrumental");
|
||||
expanded.push('pre_refrain_instrumental')
|
||||
}
|
||||
|
||||
result = expanded;
|
||||
result = expanded
|
||||
}
|
||||
|
||||
const introIndex = result.findIndex(
|
||||
(item) => item === "short_intro" || item === "long_intro"
|
||||
);
|
||||
const introIndex = result.findIndex((item) => item === 'short_intro' || item === 'long_intro')
|
||||
if (introIndex > 0) {
|
||||
const [intro] = result.splice(introIndex, 1);
|
||||
result.unshift(intro);
|
||||
const [intro] = result.splice(introIndex, 1)
|
||||
result.unshift(intro)
|
||||
}
|
||||
|
||||
const outroIndex = result.findIndex((item) => {
|
||||
const meta = getSegmentMeta(item);
|
||||
return meta?.exclusiveGroup === "outro";
|
||||
});
|
||||
const meta = getSegmentMeta(item)
|
||||
return meta?.exclusiveGroup === 'outro'
|
||||
})
|
||||
|
||||
if (outroIndex !== -1 && outroIndex < result.length - 1) {
|
||||
const [outro] = result.splice(outroIndex, 1);
|
||||
result.push(outro);
|
||||
const [outro] = result.splice(outroIndex, 1)
|
||||
result.push(outro)
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
return result
|
||||
}
|
||||
|
||||
export const formatStructureLabel = (type, occurrence = 1) => {
|
||||
const normalized = normalizeStructureType(type);
|
||||
const meta = getSegmentMeta(normalized);
|
||||
const baseLabel = meta?.label || toStartCase(normalized || type);
|
||||
const showIndex = meta?.showIndex === true;
|
||||
if (showIndex) return `${baseLabel} ${occurrence}`;
|
||||
if (!meta && occurrence > 1) return `${baseLabel} ${occurrence}`;
|
||||
return baseLabel;
|
||||
};
|
||||
const normalized = normalizeStructureType(type)
|
||||
const meta = getSegmentMeta(normalized)
|
||||
const baseLabel = meta?.label || toStartCase(normalized || type)
|
||||
const showIndex = meta?.showIndex === true
|
||||
if (showIndex) return `${baseLabel} ${occurrence}`
|
||||
if (!meta && occurrence > 1) return `${baseLabel} ${occurrence}`
|
||||
return baseLabel
|
||||
}
|
||||
|
||||
export const getStructureInputHeight = (type) => {
|
||||
const normalized = normalizeStructureType(type);
|
||||
const meta = getSegmentMeta(normalized);
|
||||
return typeof meta?.inputHeight === "number" ? meta.inputHeight : undefined;
|
||||
};
|
||||
const normalized = normalizeStructureType(type)
|
||||
const meta = getSegmentMeta(normalized)
|
||||
return typeof meta?.inputHeight === 'number' ? meta.inputHeight : undefined
|
||||
}
|
||||
|
||||
export const getPromptLabelForStructure = (type) => {
|
||||
const normalized = normalizeStructureType(type);
|
||||
const meta = getSegmentMeta(normalized);
|
||||
if (meta?.promptLabel) return meta.promptLabel;
|
||||
if (meta?.label) return meta.label;
|
||||
return toStartCase(normalized || type);
|
||||
};
|
||||
const normalized = normalizeStructureType(type)
|
||||
const meta = getSegmentMeta(normalized)
|
||||
if (meta?.promptLabel) return meta.promptLabel
|
||||
if (meta?.label) return meta.label
|
||||
return toStartCase(normalized || type)
|
||||
}
|
||||
|
||||
export const isOptionalStructureType = (type) => {
|
||||
const normalized = normalizeStructureType(type);
|
||||
const meta = getSegmentMeta(normalized);
|
||||
return !!meta?.optional;
|
||||
};
|
||||
const normalized = normalizeStructureType(type)
|
||||
const meta = getSegmentMeta(normalized)
|
||||
return !!meta?.optional
|
||||
}
|
||||
|
||||
export const arraysAreSame = (a = [], b = []) => {
|
||||
if (a === b) return true;
|
||||
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
if (a === b) return true
|
||||
if (!Array.isArray(a) || !Array.isArray(b)) return false
|
||||
if (a.length !== b.length) return false
|
||||
for (let i = 0; i < a.length; i += 1) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
if (a[i] !== b[i]) return false
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user