You've already forked AstralRinth
forked from didirus/AstralRinth
Add TailwindCSS (#1252)
* Setup TailwindCSS * Fully setup configuration * Refactor some tailwind variables
This commit is contained in:
@@ -1,117 +1,118 @@
|
||||
import dayjs from 'dayjs'
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { ref, watch, computed } from "vue";
|
||||
|
||||
// note: build step can miss unix import for some reason, so
|
||||
// we have to import it like this
|
||||
|
||||
const { unix } = dayjs
|
||||
const { unix } = dayjs;
|
||||
|
||||
export function useCountryNames(style = 'long') {
|
||||
const formattingOptions = { type: 'region', style }
|
||||
const { formats } = useVIntl()
|
||||
export function useCountryNames(style = "long") {
|
||||
const formattingOptions = { type: "region", style };
|
||||
// eslint-disable-next-line no-undef
|
||||
const { formats } = useVIntl();
|
||||
return function formatCountryName(code) {
|
||||
return formats.displayName(code, formattingOptions)
|
||||
}
|
||||
return formats.displayName(code, formattingOptions);
|
||||
};
|
||||
}
|
||||
|
||||
export const countryCodeToName = (code) => {
|
||||
const formatCountryName = useCountryNames()
|
||||
const formatCountryName = useCountryNames();
|
||||
|
||||
return formatCountryName(code)
|
||||
}
|
||||
return formatCountryName(code);
|
||||
};
|
||||
|
||||
export const countryCodeToFlag = (code) => {
|
||||
if (code === 'XX') {
|
||||
return undefined
|
||||
if (code === "XX") {
|
||||
return undefined;
|
||||
}
|
||||
return `https://flagcdn.com/h240/${code.toLowerCase()}.png`
|
||||
}
|
||||
return `https://flagcdn.com/h240/${code.toLowerCase()}.png`;
|
||||
};
|
||||
|
||||
export const formatTimestamp = (timestamp) => {
|
||||
return unix(timestamp).format()
|
||||
}
|
||||
return unix(timestamp).format();
|
||||
};
|
||||
|
||||
export const formatPercent = (value, sum) => {
|
||||
return `${((value / sum) * 100).toFixed(2)}%`
|
||||
}
|
||||
return `${((value / sum) * 100).toFixed(2)}%`;
|
||||
};
|
||||
|
||||
const hashProjectId = (projectId) => {
|
||||
return projectId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % 30
|
||||
}
|
||||
return projectId.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0) % 30;
|
||||
};
|
||||
|
||||
export const defaultColors = [
|
||||
'#ff496e', // Original: Bright pink
|
||||
'#ffa347', // Original: Bright orange
|
||||
'#1bd96a', // Original: Bright green
|
||||
'#4f9cff', // Original: Bright blue
|
||||
'#c78aff', // Original: Bright purple
|
||||
'#ffeb3b', // Added: Bright yellow
|
||||
'#00bcd4', // Added: Bright cyan
|
||||
'#ff5722', // Added: Bright red-orange
|
||||
'#9c27b0', // Added: Bright deep purple
|
||||
'#3f51b5', // Added: Bright indigo
|
||||
'#009688', // Added: Bright teal
|
||||
'#cddc39', // Added: Bright lime
|
||||
'#795548', // Added: Bright brown
|
||||
'#607d8b', // Added: Bright blue-grey
|
||||
]
|
||||
"#ff496e", // Original: Bright pink
|
||||
"#ffa347", // Original: Bright orange
|
||||
"#1bd96a", // Original: Bright green
|
||||
"#4f9cff", // Original: Bright blue
|
||||
"#c78aff", // Original: Bright purple
|
||||
"#ffeb3b", // Added: Bright yellow
|
||||
"#00bcd4", // Added: Bright cyan
|
||||
"#ff5722", // Added: Bright red-orange
|
||||
"#9c27b0", // Added: Bright deep purple
|
||||
"#3f51b5", // Added: Bright indigo
|
||||
"#009688", // Added: Bright teal
|
||||
"#cddc39", // Added: Bright lime
|
||||
"#795548", // Added: Bright brown
|
||||
"#607d8b", // Added: Bright blue-grey
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {string | number} value
|
||||
* @returns {string} color
|
||||
*/
|
||||
export const getDefaultColor = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
value = hashProjectId(value)
|
||||
if (typeof value === "string") {
|
||||
value = hashProjectId(value);
|
||||
}
|
||||
return defaultColors[value % defaultColors.length]
|
||||
}
|
||||
return defaultColors[value % defaultColors.length];
|
||||
};
|
||||
|
||||
export const intToRgba = (color, projectId = 'Unknown', theme = 'dark') => {
|
||||
const hash = hashProjectId(projectId)
|
||||
export const intToRgba = (color, projectId = "Unknown", theme = "dark") => {
|
||||
const hash = hashProjectId(projectId);
|
||||
|
||||
if (!color || color === 0) {
|
||||
return getDefaultColor(hash)
|
||||
return getDefaultColor(hash);
|
||||
}
|
||||
|
||||
// if color is a string, return that instead
|
||||
if (typeof color === 'string') {
|
||||
return color
|
||||
if (typeof color === "string") {
|
||||
return color;
|
||||
}
|
||||
|
||||
// Extract RGB values
|
||||
let r = (color >> 16) & 255
|
||||
let g = (color >> 8) & 255
|
||||
let b = color & 255
|
||||
let r = (color >> 16) & 255;
|
||||
let g = (color >> 8) & 255;
|
||||
let b = color & 255;
|
||||
|
||||
// Hash function to alter color slightly based on project_id
|
||||
r = (r + hash) % 256
|
||||
g = (g + hash) % 256
|
||||
b = (b + hash) % 256
|
||||
r = (r + hash) % 256;
|
||||
g = (g + hash) % 256;
|
||||
b = (b + hash) % 256;
|
||||
|
||||
// Adjust brightness for theme
|
||||
const brightness = r * 0.299 + g * 0.587 + b * 0.114
|
||||
const threshold = theme === 'dark' ? 50 : 200
|
||||
if (theme === 'dark' && brightness < threshold) {
|
||||
const brightness = r * 0.299 + g * 0.587 + b * 0.114;
|
||||
const threshold = theme === "dark" ? 50 : 200;
|
||||
if (theme === "dark" && brightness < threshold) {
|
||||
// Increase brightness for dark theme
|
||||
r += threshold / 2
|
||||
g += threshold / 2
|
||||
b += threshold / 2
|
||||
} else if (theme === 'light' && brightness > threshold) {
|
||||
r += threshold / 2;
|
||||
g += threshold / 2;
|
||||
b += threshold / 2;
|
||||
} else if (theme === "light" && brightness > threshold) {
|
||||
// Decrease brightness for light theme
|
||||
r -= threshold / 4
|
||||
g -= threshold / 4
|
||||
b -= threshold / 4
|
||||
r -= threshold / 4;
|
||||
g -= threshold / 4;
|
||||
b -= threshold / 4;
|
||||
}
|
||||
|
||||
// Ensure RGB values are within 0-255
|
||||
r = Math.min(255, Math.max(0, r))
|
||||
g = Math.min(255, Math.max(0, g))
|
||||
b = Math.min(255, Math.max(0, b))
|
||||
r = Math.min(255, Math.max(0, r));
|
||||
g = Math.min(255, Math.max(0, g));
|
||||
b = Math.min(255, Math.max(0, b));
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, 1)`
|
||||
}
|
||||
return `rgba(${r}, ${g}, ${b}, 1)`;
|
||||
};
|
||||
|
||||
const emptyAnalytics = {
|
||||
sum: 0,
|
||||
@@ -121,7 +122,7 @@ const emptyAnalytics = {
|
||||
data: [],
|
||||
sumData: [
|
||||
{
|
||||
name: '',
|
||||
name: "",
|
||||
data: [],
|
||||
},
|
||||
],
|
||||
@@ -129,78 +130,78 @@ const emptyAnalytics = {
|
||||
defaultColors: [],
|
||||
},
|
||||
projectIds: [],
|
||||
}
|
||||
};
|
||||
|
||||
export const analyticsSetToCSVString = (analytics) => {
|
||||
if (!analytics) {
|
||||
return ''
|
||||
return "";
|
||||
}
|
||||
|
||||
const newline = '\n'
|
||||
const labels = analytics.chart.labels
|
||||
const projects = analytics.chart.data
|
||||
const newline = "\n";
|
||||
const labels = analytics.chart.labels;
|
||||
const projects = analytics.chart.data;
|
||||
|
||||
const projectNames = projects.map((p) => p.name)
|
||||
const projectNames = projects.map((p) => p.name);
|
||||
|
||||
const header = ['Date', ...projectNames].join(',')
|
||||
const header = ["Date", ...projectNames].join(",");
|
||||
|
||||
const data = labels.map((label, i) => {
|
||||
const values = projects.map((p) => p.data?.[i] || '')
|
||||
return [label, ...values].join(',')
|
||||
})
|
||||
const values = projects.map((p) => p.data?.[i] || "");
|
||||
return [label, ...values].join(",");
|
||||
});
|
||||
|
||||
return [header, ...data].join(newline)
|
||||
}
|
||||
return [header, ...data].join(newline);
|
||||
};
|
||||
|
||||
export const processAnalytics = (category, projects, labelFn, sortFn, mapFn, chartName) => {
|
||||
if (!category || !projects) {
|
||||
return emptyAnalytics
|
||||
return emptyAnalytics;
|
||||
}
|
||||
|
||||
// Get an intersection of category keys and project ids
|
||||
const projectIds = projects.map((p) => p.id)
|
||||
const loadedProjectIds = Object.keys(category).filter((id) => projectIds.includes(id))
|
||||
const projectIds = projects.map((p) => p.id);
|
||||
const loadedProjectIds = Object.keys(category).filter((id) => projectIds.includes(id));
|
||||
|
||||
if (!loadedProjectIds?.length) {
|
||||
return emptyAnalytics
|
||||
return emptyAnalytics;
|
||||
}
|
||||
|
||||
const loadedProjectData = loadedProjectIds.map((id) => category[id])
|
||||
const loadedProjectData = loadedProjectIds.map((id) => category[id]);
|
||||
|
||||
// Convert each project's data into a list of [unix_ts_str, number] pairs
|
||||
const projectData = loadedProjectData
|
||||
.map((data) => Object.entries(data))
|
||||
.map((data) => data.sort(sortFn))
|
||||
.map((data) => (mapFn ? data.map(mapFn) : data))
|
||||
.map((data) => (mapFn ? data.map(mapFn) : data));
|
||||
|
||||
// Each project may not include the same timestamps, so we should use the union of all timestamps
|
||||
const timestamps = Array.from(
|
||||
new Set(projectData.flatMap((data) => data.map(([ts]) => ts)))
|
||||
).sort()
|
||||
new Set(projectData.flatMap((data) => data.map(([ts]) => ts))),
|
||||
).sort();
|
||||
|
||||
const chartData = projectData
|
||||
.map((data, i) => {
|
||||
const project = projects.find((p) => p.id === loadedProjectIds[i])
|
||||
const project = projects.find((p) => p.id === loadedProjectIds[i]);
|
||||
if (!project) {
|
||||
throw new Error(`Project ${loadedProjectIds[i]} not found`)
|
||||
throw new Error(`Project ${loadedProjectIds[i]} not found`);
|
||||
}
|
||||
|
||||
return {
|
||||
name: `${project.title}`,
|
||||
data: timestamps.map((ts) => {
|
||||
const entry = data.find(([ets]) => ets === ts)
|
||||
return entry ? entry[1] : 0
|
||||
const entry = data.find(([ets]) => ets === ts);
|
||||
return entry ? entry[1] : 0;
|
||||
}),
|
||||
id: project.id,
|
||||
color: project.color,
|
||||
}
|
||||
};
|
||||
})
|
||||
.sort(
|
||||
(a, b) =>
|
||||
b.data.reduce((acc, cur) => acc + cur, 0) - a.data.reduce((acc, cur) => acc + cur, 0)
|
||||
)
|
||||
b.data.reduce((acc, cur) => acc + cur, 0) - a.data.reduce((acc, cur) => acc + cur, 0),
|
||||
);
|
||||
|
||||
const projectIdsSortedBySum = chartData.map((p) => p.id)
|
||||
const projectIdsSortedBySum = chartData.map((p) => p.id);
|
||||
|
||||
return {
|
||||
// The total count of all the values across all projects
|
||||
@@ -213,25 +214,26 @@ export const processAnalytics = (category, projects, labelFn, sortFn, mapFn, cha
|
||||
{
|
||||
name: chartName,
|
||||
data: timestamps.map((ts) => {
|
||||
const entries = projectData.flat().filter(([ets]) => ets === ts)
|
||||
return entries.reduce((acc, cur) => acc + cur[1], 0)
|
||||
const entries = projectData.flat().filter(([ets]) => ets === ts);
|
||||
return entries.reduce((acc, cur) => acc + cur[1], 0);
|
||||
}),
|
||||
},
|
||||
],
|
||||
colors: projectData.map((_, i) => {
|
||||
const theme = useTheme()
|
||||
const project = chartData[i]
|
||||
// eslint-disable-next-line no-undef
|
||||
const theme = useTheme();
|
||||
const project = chartData[i];
|
||||
|
||||
return intToRgba(project.color, project.id, theme.value)
|
||||
return intToRgba(project.color, project.id, theme.value);
|
||||
}),
|
||||
defaultColors: projectData.map((_, i) => {
|
||||
const project = chartData[i]
|
||||
return getDefaultColor(project.id)
|
||||
const project = chartData[i];
|
||||
return getDefaultColor(project.id);
|
||||
}),
|
||||
},
|
||||
projectIds: projectIdsSortedBySum,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const processAnalyticsByCountry = (category, projects, sortFn) => {
|
||||
if (!category || !projects) {
|
||||
@@ -239,62 +241,64 @@ export const processAnalyticsByCountry = (category, projects, sortFn) => {
|
||||
sum: 0,
|
||||
len: 0,
|
||||
data: [],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Get an intersection of category keys and project ids
|
||||
const projectIds = projects.map((p) => p.id)
|
||||
const loadedProjectIds = Object.keys(category).filter((id) => projectIds.includes(id))
|
||||
const projectIds = projects.map((p) => p.id);
|
||||
const loadedProjectIds = Object.keys(category).filter((id) => projectIds.includes(id));
|
||||
|
||||
if (!loadedProjectIds?.length) {
|
||||
return {
|
||||
sum: 0,
|
||||
len: 0,
|
||||
data: [],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const loadedProjectData = loadedProjectIds.map((id) => category[id])
|
||||
const loadedProjectData = loadedProjectIds.map((id) => category[id]);
|
||||
|
||||
// Convert each project's data into a list of [countrycode, number] pairs
|
||||
// Fold into a single list with summed values for each country over all projects
|
||||
|
||||
const countrySums = new Map()
|
||||
const countrySums = new Map();
|
||||
|
||||
loadedProjectData.forEach((data) => {
|
||||
Object.entries(data).forEach(([country, value]) => {
|
||||
const current = countrySums.get(country) || 0
|
||||
countrySums.set(country, current + value)
|
||||
})
|
||||
})
|
||||
const current = countrySums.get(country) || 0;
|
||||
countrySums.set(country, current + value);
|
||||
});
|
||||
});
|
||||
|
||||
const entries = Array.from(countrySums.entries())
|
||||
const entries = Array.from(countrySums.entries());
|
||||
|
||||
return {
|
||||
sum: entries.reduce((acc, cur) => acc + cur[1], 0),
|
||||
len: entries.length,
|
||||
data: entries.sort(sortFn),
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const sortCount = ([_a, a], [_b, b]) => b - a
|
||||
const sortTimestamp = ([a], [b]) => a - b
|
||||
const roundValue = ([ts, value]) => [ts, Math.round(parseFloat(value) * 100) / 100]
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const sortCount = ([_a, a], [_b, b]) => b - a;
|
||||
const sortTimestamp = ([a], [b]) => a - b;
|
||||
const roundValue = ([ts, value]) => [ts, Math.round(parseFloat(value) * 100) / 100];
|
||||
|
||||
const processCountryAnalytics = (c, projects) => processAnalyticsByCountry(c, projects, sortCount)
|
||||
const processCountryAnalytics = (c, projects) => processAnalyticsByCountry(c, projects, sortCount);
|
||||
const processNumberAnalytics = (c, projects) =>
|
||||
processAnalytics(c, projects, formatTimestamp, sortTimestamp, null, 'Downloads')
|
||||
processAnalytics(c, projects, formatTimestamp, sortTimestamp, null, "Downloads");
|
||||
const processRevAnalytics = (c, projects) =>
|
||||
processAnalytics(c, projects, formatTimestamp, sortTimestamp, roundValue, 'Revenue')
|
||||
processAnalytics(c, projects, formatTimestamp, sortTimestamp, roundValue, "Revenue");
|
||||
|
||||
const useFetchAnalytics = (
|
||||
url,
|
||||
baseOptions = {
|
||||
apiVersion: 3,
|
||||
}
|
||||
},
|
||||
) => {
|
||||
return useBaseFetch(url, baseOptions)
|
||||
}
|
||||
// eslint-disable-next-line no-undef
|
||||
return useBaseFetch(url, baseOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Ref<any[]>} projects
|
||||
@@ -304,21 +308,21 @@ export const useFetchAllAnalytics = (
|
||||
onDataRefresh,
|
||||
projects,
|
||||
selectedProjects,
|
||||
personalRevenue = false
|
||||
personalRevenue = false,
|
||||
) => {
|
||||
const timeResolution = ref(1440) // 1 day
|
||||
const timeRange = ref(43200) // 30 days
|
||||
const timeResolution = ref(1440); // 1 day
|
||||
const timeRange = ref(43200); // 30 days
|
||||
|
||||
const startDate = ref(Date.now() - timeRange.value * 60 * 1000)
|
||||
const endDate = ref(Date.now())
|
||||
const startDate = ref(Date.now() - timeRange.value * 60 * 1000);
|
||||
const endDate = ref(Date.now());
|
||||
|
||||
const downloadData = ref(null)
|
||||
const viewData = ref(null)
|
||||
const revenueData = ref(null)
|
||||
const downloadsByCountry = ref(null)
|
||||
const viewsByCountry = ref(null)
|
||||
const loading = ref(true)
|
||||
const error = ref(null)
|
||||
const downloadData = ref(null);
|
||||
const viewData = ref(null);
|
||||
const revenueData = ref(null);
|
||||
const downloadsByCountry = ref(null);
|
||||
const viewsByCountry = ref(null);
|
||||
const loading = ref(true);
|
||||
const error = ref(null);
|
||||
|
||||
const formattedData = computed(() => ({
|
||||
downloads: processNumberAnalytics(downloadData.value, selectedProjects.value),
|
||||
@@ -326,28 +330,28 @@ export const useFetchAllAnalytics = (
|
||||
revenue: processRevAnalytics(revenueData.value, selectedProjects.value),
|
||||
downloadsByCountry: processCountryAnalytics(downloadsByCountry.value, selectedProjects.value),
|
||||
viewsByCountry: processCountryAnalytics(viewsByCountry.value, selectedProjects.value),
|
||||
}))
|
||||
}));
|
||||
|
||||
const totalData = computed(() => ({
|
||||
downloads: processNumberAnalytics(downloadData.value, projects.value),
|
||||
views: processNumberAnalytics(viewData.value, projects.value),
|
||||
revenue: processRevAnalytics(revenueData.value, projects.value),
|
||||
}))
|
||||
}));
|
||||
|
||||
const fetchData = async (query) => {
|
||||
const normalQuery = new URLSearchParams(query)
|
||||
const revenueQuery = new URLSearchParams(query)
|
||||
const normalQuery = new URLSearchParams(query);
|
||||
const revenueQuery = new URLSearchParams(query);
|
||||
|
||||
if (personalRevenue) {
|
||||
revenueQuery.delete('project_ids')
|
||||
revenueQuery.delete("project_ids");
|
||||
}
|
||||
|
||||
const qs = normalQuery.toString()
|
||||
const revenueQs = revenueQuery.toString()
|
||||
const qs = normalQuery.toString();
|
||||
const revenueQs = revenueQuery.toString();
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const responses = await Promise.all([
|
||||
useFetchAnalytics(`analytics/downloads?${qs}`),
|
||||
@@ -355,39 +359,39 @@ export const useFetchAllAnalytics = (
|
||||
useFetchAnalytics(`analytics/revenue?${revenueQs}`),
|
||||
useFetchAnalytics(`analytics/countries/downloads?${qs}`),
|
||||
useFetchAnalytics(`analytics/countries/views?${qs}`),
|
||||
])
|
||||
]);
|
||||
|
||||
// collect project ids from projects.value into a set
|
||||
const projectIds = new Set()
|
||||
const projectIds = new Set();
|
||||
if (projects.value) {
|
||||
projects.value.forEach((p) => projectIds.add(p.id))
|
||||
projects.value.forEach((p) => projectIds.add(p.id));
|
||||
} else {
|
||||
// if projects.value is not set, we assume that we want all project ids
|
||||
Object.keys(responses[0] || {}).forEach((id) => projectIds.add(id))
|
||||
Object.keys(responses[0] || {}).forEach((id) => projectIds.add(id));
|
||||
}
|
||||
|
||||
const filterProjectIds = (data) => {
|
||||
const filtered = {}
|
||||
const filtered = {};
|
||||
Object.entries(data).forEach(([id, values]) => {
|
||||
if (projectIds.has(id)) {
|
||||
filtered[id] = values
|
||||
filtered[id] = values;
|
||||
}
|
||||
})
|
||||
return filtered
|
||||
}
|
||||
});
|
||||
return filtered;
|
||||
};
|
||||
|
||||
downloadData.value = filterProjectIds(responses[0] || {})
|
||||
viewData.value = filterProjectIds(responses[1] || {})
|
||||
revenueData.value = filterProjectIds(responses[2] || {})
|
||||
downloadData.value = filterProjectIds(responses[0] || {});
|
||||
viewData.value = filterProjectIds(responses[1] || {});
|
||||
revenueData.value = filterProjectIds(responses[2] || {});
|
||||
|
||||
downloadsByCountry.value = responses[3] || {}
|
||||
viewsByCountry.value = responses[4] || {}
|
||||
downloadsByCountry.value = responses[3] || {};
|
||||
viewsByCountry.value = responses[4] || {};
|
||||
} catch (e) {
|
||||
error.value = e
|
||||
error.value = e;
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
[() => startDate.value, () => endDate.value, () => timeResolution.value, () => projects.value],
|
||||
@@ -396,32 +400,32 @@ export const useFetchAllAnalytics = (
|
||||
start_date: dayjs(startDate.value).toISOString(),
|
||||
end_date: dayjs(endDate.value).toISOString(),
|
||||
resolution_minutes: timeResolution.value,
|
||||
}
|
||||
};
|
||||
|
||||
if (projects.value?.length) {
|
||||
q.project_ids = JSON.stringify(projects.value.map((p) => p.id))
|
||||
q.project_ids = JSON.stringify(projects.value.map((p) => p.id));
|
||||
}
|
||||
|
||||
await fetchData(q)
|
||||
await fetchData(q);
|
||||
|
||||
if (onDataRefresh) {
|
||||
onDataRefresh()
|
||||
onDataRefresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
const validProjectIds = computed(() => {
|
||||
const ids = new Set()
|
||||
const ids = new Set();
|
||||
|
||||
if (downloadData.value) {
|
||||
Object.keys(downloadData.value).forEach((id) => ids.add(id))
|
||||
Object.keys(downloadData.value).forEach((id) => ids.add(id));
|
||||
}
|
||||
|
||||
if (viewData.value) {
|
||||
Object.keys(viewData.value).forEach((id) => ids.add(id))
|
||||
Object.keys(viewData.value).forEach((id) => ids.add(id));
|
||||
}
|
||||
|
||||
if (revenueData.value) {
|
||||
@@ -429,14 +433,14 @@ export const useFetchAllAnalytics = (
|
||||
Object.entries(revenueData.value).forEach(([id, data]) => {
|
||||
if (Object.keys(data).length) {
|
||||
if (Object.values(data).some((v) => v >= 0.01)) {
|
||||
ids.add(id)
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return Array.from(ids)
|
||||
})
|
||||
return Array.from(ids);
|
||||
});
|
||||
|
||||
return {
|
||||
// Configuration
|
||||
@@ -459,5 +463,5 @@ export const useFetchAllAnalytics = (
|
||||
totalData,
|
||||
loading,
|
||||
error,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,177 +1,177 @@
|
||||
export const commonMessages = defineMessages({
|
||||
allProjectType: {
|
||||
id: 'project-type.all',
|
||||
defaultMessage: 'All',
|
||||
id: "project-type.all",
|
||||
defaultMessage: "All",
|
||||
},
|
||||
cancelButton: {
|
||||
id: 'button.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
id: "button.cancel",
|
||||
defaultMessage: "Cancel",
|
||||
},
|
||||
collectionsLabel: {
|
||||
id: 'label.collections',
|
||||
defaultMessage: 'Collections',
|
||||
id: "label.collections",
|
||||
defaultMessage: "Collections",
|
||||
},
|
||||
continueButton: {
|
||||
id: 'button.continue',
|
||||
defaultMessage: 'Continue',
|
||||
id: "button.continue",
|
||||
defaultMessage: "Continue",
|
||||
},
|
||||
changesSavedLabel: {
|
||||
id: 'label.changes-saved',
|
||||
defaultMessage: 'Changes saved',
|
||||
id: "label.changes-saved",
|
||||
defaultMessage: "Changes saved",
|
||||
},
|
||||
createAProjectButton: {
|
||||
id: 'button.create-a-project',
|
||||
defaultMessage: 'Create a project',
|
||||
id: "button.create-a-project",
|
||||
defaultMessage: "Create a project",
|
||||
},
|
||||
createdAgoLabel: {
|
||||
id: 'label.created-ago',
|
||||
defaultMessage: 'Created {ago}',
|
||||
id: "label.created-ago",
|
||||
defaultMessage: "Created {ago}",
|
||||
},
|
||||
dashboardLabel: {
|
||||
id: 'label.dashboard',
|
||||
defaultMessage: 'Dashboard',
|
||||
id: "label.dashboard",
|
||||
defaultMessage: "Dashboard",
|
||||
},
|
||||
dateAtTimeTooltip: {
|
||||
id: 'tooltip.date-at-time',
|
||||
defaultMessage: '{date, date, long} at {time, time, short}',
|
||||
id: "tooltip.date-at-time",
|
||||
defaultMessage: "{date, date, long} at {time, time, short}",
|
||||
},
|
||||
deleteLabel: {
|
||||
id: 'label.delete',
|
||||
defaultMessage: 'Delete',
|
||||
id: "label.delete",
|
||||
defaultMessage: "Delete",
|
||||
},
|
||||
descriptionLabel: {
|
||||
id: 'label.description',
|
||||
defaultMessage: 'Description',
|
||||
id: "label.description",
|
||||
defaultMessage: "Description",
|
||||
},
|
||||
editButton: {
|
||||
id: 'button.edit',
|
||||
defaultMessage: 'Edit',
|
||||
id: "button.edit",
|
||||
defaultMessage: "Edit",
|
||||
},
|
||||
errorLabel: {
|
||||
id: 'label.error',
|
||||
defaultMessage: 'Error',
|
||||
id: "label.error",
|
||||
defaultMessage: "Error",
|
||||
},
|
||||
errorNotificationTitle: {
|
||||
id: 'notification.error.title',
|
||||
defaultMessage: 'An error occurred',
|
||||
id: "notification.error.title",
|
||||
defaultMessage: "An error occurred",
|
||||
},
|
||||
followedProjectsLabel: {
|
||||
id: 'label.followed-projects',
|
||||
defaultMessage: 'Followed projects',
|
||||
id: "label.followed-projects",
|
||||
defaultMessage: "Followed projects",
|
||||
},
|
||||
galleryInputView: {
|
||||
id: 'input.view.gallery',
|
||||
defaultMessage: 'Gallery view',
|
||||
id: "input.view.gallery",
|
||||
defaultMessage: "Gallery view",
|
||||
},
|
||||
gridInputView: {
|
||||
id: 'input.view.grid',
|
||||
defaultMessage: 'Grid view',
|
||||
id: "input.view.grid",
|
||||
defaultMessage: "Grid view",
|
||||
},
|
||||
listInputView: {
|
||||
id: 'input.view.list',
|
||||
defaultMessage: 'Rows view',
|
||||
id: "input.view.list",
|
||||
defaultMessage: "Rows view",
|
||||
},
|
||||
moderationLabel: {
|
||||
id: 'label.moderation',
|
||||
defaultMessage: 'Moderation',
|
||||
id: "label.moderation",
|
||||
defaultMessage: "Moderation",
|
||||
},
|
||||
notificationsLabel: {
|
||||
id: 'label.notifications',
|
||||
defaultMessage: 'Notifications',
|
||||
id: "label.notifications",
|
||||
defaultMessage: "Notifications",
|
||||
},
|
||||
privateLabel: {
|
||||
id: 'collection.label.private',
|
||||
defaultMessage: 'Private',
|
||||
id: "collection.label.private",
|
||||
defaultMessage: "Private",
|
||||
},
|
||||
publicLabel: {
|
||||
id: 'label.public',
|
||||
defaultMessage: 'Public',
|
||||
id: "label.public",
|
||||
defaultMessage: "Public",
|
||||
},
|
||||
rejectedLabel: {
|
||||
id: 'label.rejected',
|
||||
defaultMessage: 'Rejected',
|
||||
id: "label.rejected",
|
||||
defaultMessage: "Rejected",
|
||||
},
|
||||
passwordLabel: {
|
||||
id: 'label.password',
|
||||
defaultMessage: 'Password',
|
||||
id: "label.password",
|
||||
defaultMessage: "Password",
|
||||
},
|
||||
saveButton: {
|
||||
id: 'button.save',
|
||||
defaultMessage: 'Save',
|
||||
id: "button.save",
|
||||
defaultMessage: "Save",
|
||||
},
|
||||
saveChangesButton: {
|
||||
id: 'button.save-changes',
|
||||
defaultMessage: 'Save changes',
|
||||
id: "button.save-changes",
|
||||
defaultMessage: "Save changes",
|
||||
},
|
||||
scopesLabel: {
|
||||
id: 'label.scopes',
|
||||
defaultMessage: 'Scopes',
|
||||
id: "label.scopes",
|
||||
defaultMessage: "Scopes",
|
||||
},
|
||||
settingsLabel: {
|
||||
id: 'label.settings',
|
||||
defaultMessage: 'Settings',
|
||||
id: "label.settings",
|
||||
defaultMessage: "Settings",
|
||||
},
|
||||
signInButton: {
|
||||
id: 'button.sign-in',
|
||||
defaultMessage: 'Sign in',
|
||||
id: "button.sign-in",
|
||||
defaultMessage: "Sign in",
|
||||
},
|
||||
signOutButton: {
|
||||
id: 'button.sign-out',
|
||||
defaultMessage: 'Sign out',
|
||||
id: "button.sign-out",
|
||||
defaultMessage: "Sign out",
|
||||
},
|
||||
titleLabel: {
|
||||
id: 'label.title',
|
||||
defaultMessage: 'Title',
|
||||
id: "label.title",
|
||||
defaultMessage: "Title",
|
||||
},
|
||||
unlistedLabel: {
|
||||
id: 'label.unlisted',
|
||||
defaultMessage: 'Unlisted',
|
||||
id: "label.unlisted",
|
||||
defaultMessage: "Unlisted",
|
||||
},
|
||||
uploadImageButton: {
|
||||
id: 'button.upload-image',
|
||||
defaultMessage: 'Upload image',
|
||||
id: "button.upload-image",
|
||||
defaultMessage: "Upload image",
|
||||
},
|
||||
visibilityLabel: {
|
||||
id: 'label.visibility',
|
||||
defaultMessage: 'Visibility',
|
||||
id: "label.visibility",
|
||||
defaultMessage: "Visibility",
|
||||
},
|
||||
visitYourProfile: {
|
||||
id: 'label.visit-your-profile',
|
||||
defaultMessage: 'Visit your profile',
|
||||
id: "label.visit-your-profile",
|
||||
defaultMessage: "Visit your profile",
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
export const commonSettingsMessages = defineMessages({
|
||||
appearance: {
|
||||
id: 'settings.appearance.title',
|
||||
defaultMessage: 'Appearance',
|
||||
id: "settings.appearance.title",
|
||||
defaultMessage: "Appearance",
|
||||
},
|
||||
language: {
|
||||
id: 'settings.language.title',
|
||||
defaultMessage: 'Language',
|
||||
id: "settings.language.title",
|
||||
defaultMessage: "Language",
|
||||
},
|
||||
profile: {
|
||||
id: 'settings.profile.title',
|
||||
defaultMessage: 'Public profile',
|
||||
id: "settings.profile.title",
|
||||
defaultMessage: "Public profile",
|
||||
},
|
||||
account: {
|
||||
id: 'settings.account.title',
|
||||
defaultMessage: 'Account and security',
|
||||
id: "settings.account.title",
|
||||
defaultMessage: "Account and security",
|
||||
},
|
||||
authorizedApps: {
|
||||
id: 'settings.authorized-apps.title',
|
||||
defaultMessage: 'Authorized apps',
|
||||
id: "settings.authorized-apps.title",
|
||||
defaultMessage: "Authorized apps",
|
||||
},
|
||||
sessions: {
|
||||
id: 'settings.sessions.title',
|
||||
defaultMessage: 'Sessions',
|
||||
id: "settings.sessions.title",
|
||||
defaultMessage: "Sessions",
|
||||
},
|
||||
pats: {
|
||||
id: 'settings.pats.title',
|
||||
defaultMessage: 'Personal access tokens',
|
||||
id: "settings.pats.title",
|
||||
defaultMessage: "Personal access tokens",
|
||||
},
|
||||
applications: {
|
||||
id: 'settings.applications.title',
|
||||
defaultMessage: 'Your applications',
|
||||
id: "settings.applications.title",
|
||||
defaultMessage: "Your applications",
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
const projectTypeMessages = defineMessages({
|
||||
datapack: {
|
||||
id: 'project-type.datapack.singular',
|
||||
defaultMessage: 'Data Pack',
|
||||
id: "project-type.datapack.singular",
|
||||
defaultMessage: "Data Pack",
|
||||
},
|
||||
datapacks: {
|
||||
id: 'project-type.datapack.plural',
|
||||
defaultMessage: 'Data Packs',
|
||||
id: "project-type.datapack.plural",
|
||||
defaultMessage: "Data Packs",
|
||||
},
|
||||
mod: {
|
||||
id: 'project-type.mod.singular',
|
||||
defaultMessage: 'Mod',
|
||||
id: "project-type.mod.singular",
|
||||
defaultMessage: "Mod",
|
||||
},
|
||||
mods: {
|
||||
id: 'project-type.mod.plural',
|
||||
defaultMessage: 'Mods',
|
||||
id: "project-type.mod.plural",
|
||||
defaultMessage: "Mods",
|
||||
},
|
||||
modpack: {
|
||||
id: 'project-type.modpack.singular',
|
||||
defaultMessage: 'Modpack',
|
||||
id: "project-type.modpack.singular",
|
||||
defaultMessage: "Modpack",
|
||||
},
|
||||
modpacks: {
|
||||
id: 'project-type.modpack.plural',
|
||||
defaultMessage: 'Modpacks',
|
||||
id: "project-type.modpack.plural",
|
||||
defaultMessage: "Modpacks",
|
||||
},
|
||||
plugin: {
|
||||
id: 'project-type.plugin.singular',
|
||||
defaultMessage: 'Plugin',
|
||||
id: "project-type.plugin.singular",
|
||||
defaultMessage: "Plugin",
|
||||
},
|
||||
plugins: {
|
||||
id: 'project-type.plugin.plural',
|
||||
defaultMessage: 'Plugins',
|
||||
id: "project-type.plugin.plural",
|
||||
defaultMessage: "Plugins",
|
||||
},
|
||||
resourcepack: {
|
||||
id: 'project-type.resourcepack.singular',
|
||||
defaultMessage: 'Resource Pack',
|
||||
id: "project-type.resourcepack.singular",
|
||||
defaultMessage: "Resource Pack",
|
||||
},
|
||||
resourcepacks: {
|
||||
id: 'project-type.resourcepack.plural',
|
||||
defaultMessage: 'Resource Packs',
|
||||
id: "project-type.resourcepack.plural",
|
||||
defaultMessage: "Resource Packs",
|
||||
},
|
||||
shader: {
|
||||
id: 'project-type.shader.singular',
|
||||
defaultMessage: 'Shader',
|
||||
id: "project-type.shader.singular",
|
||||
defaultMessage: "Shader",
|
||||
},
|
||||
shaders: {
|
||||
id: 'project-type.shader.plural',
|
||||
defaultMessage: 'Shaders',
|
||||
id: "project-type.shader.plural",
|
||||
defaultMessage: "Shaders",
|
||||
},
|
||||
project: {
|
||||
id: 'project-type.project.singular',
|
||||
defaultMessage: 'Project',
|
||||
id: "project-type.project.singular",
|
||||
defaultMessage: "Project",
|
||||
},
|
||||
projects: {
|
||||
id: 'project-type.project.plural',
|
||||
defaultMessage: 'Projects',
|
||||
id: "project-type.project.plural",
|
||||
defaultMessage: "Projects",
|
||||
},
|
||||
collection: {
|
||||
id: 'project-type.collection.singular',
|
||||
defaultMessage: 'Collection',
|
||||
id: "project-type.collection.singular",
|
||||
defaultMessage: "Collection",
|
||||
},
|
||||
collections: {
|
||||
id: 'project-type.collection.plural',
|
||||
defaultMessage: 'Collections',
|
||||
id: "project-type.collection.plural",
|
||||
defaultMessage: "Collections",
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
type ExtractSingulars<K extends string> = K extends `${infer T}s` ? T : never
|
||||
type ExtractSingulars<K extends string> = K extends `${infer T}s` ? T : never;
|
||||
|
||||
type ProjectType = ExtractSingulars<keyof typeof projectTypeMessages>
|
||||
type ProjectType = ExtractSingulars<keyof typeof projectTypeMessages>;
|
||||
|
||||
export function getProjectTypeMessage(type: ProjectType, plural = false) {
|
||||
return (
|
||||
projectTypeMessages[`${type}${plural ? 's' : ''}`] ??
|
||||
projectTypeMessages[`project${plural ? 's' : ''}`]
|
||||
)
|
||||
projectTypeMessages[`${type}${plural ? "s" : ""}`] ??
|
||||
projectTypeMessages[`project${plural ? "s" : ""}`]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const isPermission = (perms?: number, bitflag?: number) => {
|
||||
if (!perms || !bitflag) return false
|
||||
return (perms & bitflag) === bitflag
|
||||
}
|
||||
if (!perms || !bitflag) return false;
|
||||
return (perms & bitflag) === bitflag;
|
||||
};
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
const startReport = (type: string, id: string) => {
|
||||
const prefill = new URLSearchParams()
|
||||
const prefill = new URLSearchParams();
|
||||
|
||||
// type
|
||||
prefill.set('item', type)
|
||||
prefill.set('itemID', id)
|
||||
prefill.set("item", type);
|
||||
prefill.set("itemID", id);
|
||||
|
||||
navigateTo('/report?' + prefill.toString())
|
||||
}
|
||||
navigateTo("/report?" + prefill.toString());
|
||||
};
|
||||
|
||||
export const reportProject = (id: string) => {
|
||||
return startReport('project', id)
|
||||
}
|
||||
return startReport("project", id);
|
||||
};
|
||||
|
||||
export const reportVersion = (id: string) => {
|
||||
return startReport('version', id)
|
||||
}
|
||||
return startReport("version", id);
|
||||
};
|
||||
|
||||
export const reportUser = (id: string) => {
|
||||
return startReport('user', id)
|
||||
}
|
||||
return startReport("user", id);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createTextVNode, isVNode, toDisplayString, type VNode } from 'vue'
|
||||
import { createTextVNode, isVNode, toDisplayString, type VNode } from "vue";
|
||||
|
||||
/**
|
||||
* Checks whether a specific child is a VNode. If not, converts it to a display
|
||||
@@ -9,7 +9,7 @@ import { createTextVNode, isVNode, toDisplayString, type VNode } from 'vue'
|
||||
* to a display string.
|
||||
*/
|
||||
function normalizeChild(child: any): VNode {
|
||||
return isVNode(child) ? child : createTextVNode(toDisplayString(child))
|
||||
return isVNode(child) ? child : createTextVNode(toDisplayString(child));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,5 +21,5 @@ function normalizeChild(child: any): VNode {
|
||||
* @returns Children with all of non-VNodes converted to display strings.
|
||||
*/
|
||||
export function normalizeChildren(children: any | any[]): VNode[] {
|
||||
return Array.isArray(children) ? children.map(normalizeChild) : [normalizeChild(children)]
|
||||
return Array.isArray(children) ? children.map(normalizeChild) : [normalizeChild(children)];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user