diff --git a/src/common/color.ts b/src/common/color.ts new file mode 100644 index 0000000..bbc0c70 --- /dev/null +++ b/src/common/color.ts @@ -0,0 +1,99 @@ +/** + * Converts an RGB color value to HSL. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes r, g, and b are contained in the set [0, 255] and + * returns h, s, and l in the set [0, 1]. + * + * @return Array The HSL representation + * @param r + * @param g + * @param b + */ +export function rgbToHsl(r: number, g: number, b: number): [number, number, number] { + r /= 255; + g /= 255; + b /= 255; + + const max = Math.max(r, g, b), + min = Math.min(r, g, b); + let h, + s, + l = (max + min) / 2; + + if (max == min) { + h = s = 0; // achromatic + } else { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + default: + h = 0; + break; + } + + h /= 6; + } + + return [h, s, l]; +} + +/** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @return Array The RGB representation + * @param h + * @param s + * @param l + */ +export function hslToRgb(h: number, s: number, l: number): [number, number, number] { + let r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + function hue2rgb(p: number, q: number, t: number) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [r * 255, g * 255, b * 255]; +} + +export function hex2rgb(hex: string): [number, number, number] { + const res = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return res ? [parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)] : [0, 0, 0]; +} + +export function rgb2hex(r: number, g: number, b: number) { + return ( + '#' + + Math.round((1 << 24) + (r << 16) + (g << 8) + b) + .toString(16) + .slice(1) + ); +} diff --git a/src/common/theme.ts b/src/common/theme.ts new file mode 100644 index 0000000..d29a114 --- /dev/null +++ b/src/common/theme.ts @@ -0,0 +1,14 @@ +import RSS3 from '@/common/rss3'; +import { RSS3Asset } from 'rss3-next/types/rss3'; + +const setupTheme = (assets: RSS3Asset[]) => { + // Setup theme + const themes = RSS3.getAvailableThemes(assets); + if (themes[0]) { + document.body.classList.add(themes[0].class); + } else { + document.body.classList.remove(...document.body.classList); + } +}; + +export default setupTheme; diff --git a/src/common/types.ts b/src/common/types.ts index 4027132..0dd73a7 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -16,6 +16,7 @@ export interface GitcoinResponse { export interface NFT extends Asset { token_id: string; name?: string; + chain: 'BSC' | 'Ethereum' | 'Polygon'; description?: string | null; image_url?: string | null; image_preview_url?: string | null; @@ -39,6 +40,7 @@ export interface NFT extends Asset { } export interface GrantInfo { + id?: string; active: boolean; title?: string; slug?: string; @@ -54,12 +56,15 @@ export interface GrantInfo { export interface DonationInfo { donor: string; + adminAddr?: string; tokenAddr: string; amount: string; symbol?: string; + decimals?: number; formatedAmount?: string; timeStamp: string; txHash: string; + approach?: 'zkSync' | 'Standard'; } export interface DonationDetailByGrant { @@ -105,7 +110,7 @@ export interface GeneralAsset { animation_original_url?: string | null; title?: string; total_contribs?: number; - token_contribs: { + token_contribs?: { token: string; amount: string; }[]; @@ -119,3 +124,12 @@ export interface GeneralAsset { export interface GeneralAssetWithTags extends GeneralAsset { tags?: string[]; } + +export interface Profile { + avatar: string; + username: string; + address: string; + bio: string; + rns?: string; + displayAddress?: string; +} diff --git a/src/common/utils.ts b/src/common/utils.ts index 33d29d4..c992bce 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,103 +1,249 @@ -/** - * Converts an RGB color value to HSL. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * Assumes r, g, and b are contained in the set [0, 255] and - * returns h, s, and l in the set [0, 1]. - * - * @return Array The HSL representation - * @param r - * @param g - * @param b - */ -export function rgbToHsl(r: number, g: number, b: number): [number, number, number] { - r /= 255; - g /= 255; - b /= 255; - - const max = Math.max(r, g, b), - min = Math.min(r, g, b); - let h, - s, - l = (max + min) / 2; - - if (max == min) { - h = s = 0; // achromatic - } else { - const d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; - default: - h = 0; - break; +import RNSUtils from '@/common/rns'; +import RSS3 from '@/common/rss3'; +import config from '@/config'; +import { RSS3Account, RSS3Asset } from 'rss3-next/types/rss3'; +import { GeneralAsset, GeneralAssetWithTags } from './types'; + +const getName = () => { + return window.location.host.split('.').slice(0, -2).join('.'); +}; + +const orderPattern = new RegExp(`^${config.tags.prefix}:order:(-?\\d+)$`, 'i'); + +type TypesWithTag = RSS3Account | GeneralAssetWithTags; + +const getTaggedOrder = (tagged: TypesWithTag): number => { + if (!tagged.tags) { + return -1; + } + // const orderPattern = /^pass:order:(-?\d+)$/i; + for (const tag of tagged.tags) { + if (orderPattern.test(tag)) { + return parseInt(orderPattern.exec(tag)?.[1] || '-1'); } + } + return -1; +}; - h /= 6; +const setTaggedOrder = (tagged: TypesWithTag, order?: number): void => { + if (!tagged.tags) { + tagged.tags = []; + } else { + // const orderPattern = /^pass:order:(-?\d+)$/i; + const oldIndex = tagged.tags.findIndex((tag) => orderPattern.test(tag)); + if (oldIndex !== -1) { + tagged.tags.splice(oldIndex, 1); + } } + if (order) { + tagged.tags.push(`${config.tags.prefix}:order:${order}`); + } else { + tagged.tags.push(`${config.tags.prefix}:${config.tags.hiddenTag}`); + } +}; - return [h, s, l]; +function sortByOrderTag(taggeds: T[]): T[] { + taggeds.sort((a, b) => { + return getTaggedOrder(a) - getTaggedOrder(b); + }); + return taggeds; } -/** - * Converts an HSL color value to RGB. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * Assumes h, s, and l are contained in the set [0, 1] and - * returns r, g, and b in the set [0, 255]. - * - * @return Array The RGB representation - * @param h - * @param s - * @param l - */ -export function hslToRgb(h: number, s: number, l: number): [number, number, number] { - let r, g, b; - - if (s == 0) { - r = g = b = l; // achromatic - } else { - function hue2rgb(p: number, q: number, t: number) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; +const setOrderTag = async (taggeds: TypesWithTag[]): Promise => { + await Promise.all( + taggeds.map(async (tagged, index) => { + setTaggedOrder(tagged, index); + }), + ); + return taggeds; +}; + +const setHiddenTag = async (taggeds: TypesWithTag[]): Promise => { + await Promise.all( + taggeds.map(async (tagged) => { + setTaggedOrder(tagged); + }), + ); + return taggeds; +}; + +const mergeAssetsTags = async (assetsInRSS3File: RSS3Asset[], assetsGrabbed: GeneralAsset[]) => { + return await Promise.all( + (assetsGrabbed || []).map(async (ag: GeneralAssetWithTags) => { + const origType = ag.type; + if (config.hideUnlistedAsstes) { + ag.type = 'Invalid'; // Using as a match mark + } + for (const airf of assetsInRSS3File) { + if ( + airf.platform === ag.platform && + airf.identity === ag.identity && + airf.id === ag.id && + airf.type === origType + ) { + // Matched + ag.type = origType; // Recover type + if (airf.tags) { + ag.tags = airf.tags; + } + if (!ag.info.collection) { + ag.info.collection = 'Other'; + } + break; + } + } + return ag; + }), + ); +}; + +interface AssetsList { + listed: GeneralAssetWithTags[]; + unlisted: GeneralAssetWithTags[]; + assets: GeneralAssetWithTags[]; +} + +async function initAssets( + assetInRSS3: RSS3Asset[], + assetInAssetProfile: GeneralAsset[], + type: string, +): Promise { + const listed: GeneralAssetWithTags[] = []; + const unlisted: GeneralAssetWithTags[] = []; + + const allAssets = await utils.mergeAssetsTags(assetInRSS3, assetInAssetProfile); + + for (const asset of allAssets) { + if (asset.type.endsWith(type)) { + if (asset.tags?.includes(`${config.tags.prefix}:${config.tags.hiddenTag}`)) { + unlisted.push(asset); + } else { + listed.push(asset); + } } + } + + return { + listed: utils.sortByOrderTag(listed), + unlisted: unlisted, + assets: allAssets, + }; +} - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; +async function initAccounts(accounts: RSS3Account[]) { + const listed: RSS3Account[] = []; + const unlisted: RSS3Account[] = []; - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); + for (const account of accounts) { + if (!account.tags?.includes('hidden')) { + listed.push(account); + } else { + unlisted.push(account); + } } - return [r * 255, g * 255, b * 255]; + return { + listed: utils.sortByOrderTag(listed), + unlisted: utils.sortByOrderTag(unlisted), + }; } -export function hex2rgb(hex: string): [number, number, number] { - const res = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return res ? [parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)] : [0, 0, 0]; +async function getAddress(routerAddress: string) { + let address: string | undefined; + let ethAddress: string = ''; + let rns: string = ''; + + if (config.subDomain.isSubDomainMode) { + // Is subdomain mode + address = getName(); + } else if (routerAddress) { + address = routerAddress; + } + + if (address) { + if (address.startsWith('0x')) { + // Might be address type + // Get RNS and redirect + ethAddress = address; + rns = await RNSUtils.addr2Name(address); + if (rns !== '') { + window.location.href = + 'https://' + + rns + + '.' + + config.subDomain.rootDomain + + window.location.pathname.replace(`/${address}`, ''); + } + } else { + // RNS + rns = address; + ethAddress = (await RNSUtils.name2Addr(address)).toString(); + if (parseInt(ethAddress) === 0) { + return { ethAddress, rns }; + } + } + } + + return { ethAddress, rns }; } -export function rgb2hex(r: number, g: number, b: number) { - return ( - '#' + - Math.round((1 << 24) + (r << 16) + (g << 8) + b) - .toString(16) - .slice(1) +async function saveAssetsOrder(listed: GeneralAssetWithTags[], unlisted: GeneralAssetWithTags[]) { + const rss3 = await RSS3.get(); + await Promise.all( + listed.map((asset, index) => { + return rss3?.assets.patchTags( + { + ...asset, + }, + [`pass:order:${index}`], + ); + }), + ); + await Promise.all( + unlisted.map((asset) => { + return rss3?.assets.patchTags( + { + ...asset, + }, + ['pass:hidden'], + ); + }), ); + await rss3?.files.sync(); } -export function getName() { - return window.location.host.split('.').slice(0, -2).join('.'); +function extractEmbedFields(raw: string, fieldsEmbed: string[]) { + const fieldPattern = /<([A-Z]+?)#(.+?)>/gi; + const fields = raw.match(fieldPattern) || []; + const extracted = raw.replace(fieldPattern, ''); + const fieldsMatch: { + [key: string]: string; + } = {}; + + for (const field of fields) { + const splits = fieldPattern.exec(field) || []; + if (fieldsEmbed.includes(splits[1])) { + fieldsMatch[splits[1]] = splits[2]; + } + } + + return { + extracted, + fieldsMatch, + }; } + +const utils = { + sortByOrderTag, + setOrderTag, + setHiddenTag, + mergeAssetsTags, + initAssets, + initAccounts, + getAddress, + getName, + saveAssetsOrder, + extractEmbedFields, +}; + +export default utils; diff --git a/src/components/AccountCard.vue b/src/components/Account/AccountCard.vue similarity index 100% rename from src/components/AccountCard.vue rename to src/components/Account/AccountCard.vue diff --git a/src/components/AccountItem.vue b/src/components/Account/AccountItem.vue similarity index 94% rename from src/components/AccountItem.vue rename to src/components/Account/AccountItem.vue index 3a73011..dc2d923 100644 --- a/src/components/AccountItem.vue +++ b/src/components/Account/AccountItem.vue @@ -35,8 +35,8 @@ + + diff --git a/src/components/Card/TransCard.vue b/src/components/Card/TransCard.vue new file mode 100644 index 0000000..7d3fa75 --- /dev/null +++ b/src/components/Card/TransCard.vue @@ -0,0 +1,117 @@ + + + diff --git a/src/components/Common/Header.vue b/src/components/Common/Header.vue new file mode 100644 index 0000000..0899190 --- /dev/null +++ b/src/components/Common/Header.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/components/ImgHolder.vue b/src/components/Common/ImgHolder.vue similarity index 100% rename from src/components/ImgHolder.vue rename to src/components/Common/ImgHolder.vue diff --git a/src/components/Modal.vue b/src/components/Common/Modal.vue similarity index 100% rename from src/components/Modal.vue rename to src/components/Common/Modal.vue diff --git a/src/components/Tooltip.vue b/src/components/Common/Tooltip.vue similarity index 100% rename from src/components/Tooltip.vue rename to src/components/Common/Tooltip.vue diff --git a/src/components/ContentBadge.vue b/src/components/Content/ContentBadge.vue similarity index 100% rename from src/components/ContentBadge.vue rename to src/components/Content/ContentBadge.vue diff --git a/src/components/ContentCard.vue b/src/components/Content/ContentCard.vue similarity index 91% rename from src/components/ContentCard.vue rename to src/components/Content/ContentCard.vue index dfd0eb4..478851c 100644 --- a/src/components/ContentCard.vue +++ b/src/components/Content/ContentCard.vue @@ -15,7 +15,7 @@ diff --git a/src/components/GitcoinItem.vue b/src/components/Donation/GitcoinItem.vue similarity index 79% rename from src/components/GitcoinItem.vue rename to src/components/Donation/GitcoinItem.vue index 9077891..36f7283 100644 --- a/src/components/GitcoinItem.vue +++ b/src/components/Donation/GitcoinItem.vue @@ -5,11 +5,11 @@ 'w-10 h-10 rounded-sm': size === 'sm', 'w-16 h-16 rounded': size === 'md', 'w-full aspect-w-1 aspect-h-1 rounded': size === 'auto', + rounded: size === 'contain', }" - :style="{ - backgroundImage: `url(${imageUrl})`, - }" - /> + > + + diff --git a/src/components/FootprintItem.vue b/src/components/Footprint/FootprintItem.vue similarity index 66% rename from src/components/FootprintItem.vue rename to src/components/Footprint/FootprintItem.vue index 65886ea..026c6b3 100644 --- a/src/components/FootprintItem.vue +++ b/src/components/Footprint/FootprintItem.vue @@ -1,9 +1,13 @@