| 1 |
import { AI_HOST } from '@constants'; |
| 2 |
|
| 3 |
// Unlike getVibes this never throws; a caller falls back on an empty list. |
| 4 |
export const getPalettes = async (query = '') => { |
| 5 |
const url = new URL(`${AI_HOST}/api/palettes`); |
| 6 |
if (query) url.searchParams.set('palettes', query); |
| 7 |
|
| 8 |
const response = await fetch(url, { |
| 9 |
method: 'GET', |
| 10 |
headers: { 'Content-Type': 'application/json' }, |
| 11 |
}); |
| 12 |
|
| 13 |
if (!response.ok) return []; |
| 14 |
|
| 15 |
const { palettes } = await response.json(); |
| 16 |
return Array.isArray(palettes) ? palettes : []; |
| 17 |
}; |
| 18 |
|
| 19 |
// Without the widening, a palette no surface offers drops out of the purge union. |
| 20 |
export const getServedPalettes = async (surface, slugs) => { |
| 21 |
const asked = [...new Set([slugs].flat().filter(Boolean))]; |
| 22 |
|
| 23 |
try { |
| 24 |
return await getPalettes([surface, ...asked].join(',')); |
| 25 |
} catch { |
| 26 |
return []; |
| 27 |
} |
| 28 |
}; |
| 29 |
|
| 30 |
export const palettesBySlug = (palettes) => |
| 31 |
Object.fromEntries( |
| 32 |
(palettes ?? []).map((palette) => [palette.slug, palette]), |
| 33 |
); |
| 34 |
|
| 35 |
const splitByPreference = (palettes, preferred) => { |
| 36 |
const bySlug = palettesBySlug(palettes); |
| 37 |
const ranked = (preferred ?? []) |
| 38 |
.map((slug) => bySlug[slug]) |
| 39 |
.filter((palette, index, all) => palette && all.indexOf(palette) === index); |
| 40 |
const rest = (palettes ?? []).filter((palette) => !ranked.includes(palette)); |
| 41 |
return [ranked, rest]; |
| 42 |
}; |
| 43 |
|
| 44 |
export const rankPalettes = (palettes, preferred = []) => { |
| 45 |
const [ranked, rest] = splitByPreference(palettes, preferred); |
| 46 |
return [...ranked, ...rest]; |
| 47 |
}; |
| 48 |
|
| 49 |
// A fresh draw per call, so a capped picker hides no palette permanently. |
| 50 |
export const samplePalettes = (palettes, preferred = [], limit = Infinity) => { |
| 51 |
const [ranked, rest] = splitByPreference(palettes, preferred); |
| 52 |
return [...ranked, ...shuffle(rest)].slice(0, limit); |
| 53 |
}; |
| 54 |
|
| 55 |
const shuffle = (items) => { |
| 56 |
const drawn = [...items]; |
| 57 |
for (let i = drawn.length - 1; i > 0; i--) { |
| 58 |
const j = Math.floor(Math.random() * (i + 1)); |
| 59 |
[drawn[i], drawn[j]] = [drawn[j], drawn[i]]; |
| 60 |
} |
| 61 |
return drawn; |
| 62 |
}; |
| 63 |
|