diff --git a/.changeset/four-schools-sit.md b/.changeset/four-schools-sit.md new file mode 100644 index 0000000..63a7096 --- /dev/null +++ b/.changeset/four-schools-sit.md @@ -0,0 +1,5 @@ +--- +'intrepid-ibex': patch +--- + +icon overrides via namespaces diff --git a/src/lib/atproto/collection-icons.test.ts b/src/lib/atproto/collection-icons.test.ts new file mode 100644 index 0000000..c883ad8 --- /dev/null +++ b/src/lib/atproto/collection-icons.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest'; + +import { appLabelForCollection, collectionIconMatch } from './collection-icons'; + +describe('collection icon registry', () => { + it('maps chat.bsky collections to the Bluesky app icon', () => { + expect(collectionIconMatch('chat.bsky.convo.defs')?.icon).toBe('/atmologos/color/bluesky.svg'); + expect(appLabelForCollection('chat.bsky.convo.defs')).toBe('Bluesky'); + }); + + it('maps explicit namespace icons to their static namespace path', () => { + expect(collectionIconMatch('at.inlay.record.comment')?.icon).toBe('/icons/namespaces/at/inlay/icon.svg'); + expect(appLabelForCollection('at.inlay.record.comment')).toBe('Inlay'); + }); + + it('uses the most specific matching prefix', () => { + expect(collectionIconMatch('app.bsky.feed.post')?.icon).toBe('/atmologos/color/bluesky.svg'); + }); +}); diff --git a/src/lib/atproto/collection-icons.ts b/src/lib/atproto/collection-icons.ts index fd2ced0..74fefbe 100644 --- a/src/lib/atproto/collection-icons.ts +++ b/src/lib/atproto/collection-icons.ts @@ -1,27 +1,42 @@ -export type CollectionIconMatch = { prefix: string; label: string; icon: string }; +import type { CollectionIconMatch } from './types'; -const atmologoBasePath = '/atmologos/color'; +enum BasePath { + atmologo = '/atmologos/color', + namespace = '/icons/namespaces' +} + +const namespaceIcon = (ns: string) => `${BasePath.namespace}/${ns.replaceAll('.', '/')}/icon.svg`; export const collectionIconMatches: readonly CollectionIconMatch[] = [ - { prefix: 'app.blento.', label: 'Blento', icon: `${atmologoBasePath}/blento.svg` }, - { prefix: 'app.bsky.', label: 'Bluesky', icon: `${atmologoBasePath}/bluesky.svg` }, - { prefix: 'at.margin.', label: 'Margin', icon: `${atmologoBasePath}/margin.svg` }, - { prefix: 'blog.pckt.', label: 'Pckt', icon: `${atmologoBasePath}/pckt.svg` }, - { prefix: 'dev.npmx.', label: 'npmx', icon: `${atmologoBasePath}/npmx.svg` }, - { prefix: 'fm.plyr.', label: 'plyr.fm', icon: `${atmologoBasePath}/plyr.fm.svg` }, - { prefix: 'id.sifa.', label: 'Sifa', icon: `${atmologoBasePath}/sifa%20id.svg` }, - { prefix: 'net.anisota.', label: 'Anisota', icon: `${atmologoBasePath}/anisota.svg` }, - { prefix: 'network.cosmik.', label: 'Cosmik', icon: `${atmologoBasePath}/semble.svg` }, - { prefix: 'pub.leaflet.', label: 'Leaflet', icon: `${atmologoBasePath}/leaflet.svg` }, - { prefix: 'sh.tangled.', label: 'Tangled', icon: `${atmologoBasePath}/tangled.svg` }, - { prefix: 'site.standard.', label: 'Standard Site', icon: `${atmologoBasePath}/standard%20site.svg` }, - { prefix: 'so.sprk.', label: 'Spark', icon: `${atmologoBasePath}/spark.svg` }, - { prefix: 'social.grain.', label: 'Grain', icon: `${atmologoBasePath}/grain.svg` }, - { prefix: 'social.popfeed.', label: 'Popfeed', icon: `${atmologoBasePath}/popfeed.svg` } + { prefix: 'chat.bsky.', label: 'Bluesky', icon: `${BasePath.atmologo}/bluesky.svg` }, + { prefix: 'app.blento.', label: 'Blento', icon: `${BasePath.atmologo}/blento.svg` }, + { prefix: 'app.bsky.', label: 'Bluesky', icon: `${BasePath.atmologo}/bluesky.svg` }, + { prefix: 'at.inlay.', label: 'Inlay', icon: namespaceIcon('at.inlay') }, + { prefix: 'at.margin.', label: 'Margin', icon: `${BasePath.atmologo}/margin.svg` }, + { prefix: 'blog.pckt.', label: 'Pckt', icon: `${BasePath.atmologo}/pckt.svg` }, + { prefix: 'com.atproto.', label: 'ATProto', icon: namespaceIcon('com.atproto') }, + { prefix: 'com.germnetwork.', label: 'Germ', icon: namespaceIcon('com.germnetwork') }, + { prefix: 'community.lexicon.', label: 'Community', icon: namespaceIcon('com.atproto') }, + { prefix: 'computer.aetheros.', label: 'Aether OS', icon: namespaceIcon('computer.aetheros') }, + { prefix: 'dev.npmx.', label: 'npmx', icon: `${BasePath.atmologo}/npmx.svg` }, + { prefix: 'fm.plyr.', label: 'plyr.fm', icon: `${BasePath.atmologo}/plyr.fm.svg` }, + { prefix: 'id.sifa.', label: 'Sifa', icon: `${BasePath.atmologo}/sifa%20id.svg` }, + { prefix: 'net.anisota.', label: 'Anisota', icon: `${BasePath.atmologo}/anisota.svg` }, + { prefix: 'network.cosmik.', label: 'Cosmik', icon: `${BasePath.atmologo}/semble.svg` }, + { prefix: 'pub.leaflet.', label: 'Leaflet', icon: `${BasePath.atmologo}/leaflet.svg` }, + { prefix: 'sh.tangled.', label: 'Tangled', icon: `${BasePath.atmologo}/tangled.svg` }, + { prefix: 'site.standard.', label: 'Standard Site', icon: `${BasePath.atmologo}/standard%20site.svg` }, + { prefix: 'so.sprk.', label: 'Spark', icon: `${BasePath.atmologo}/spark.svg` }, + { prefix: 'social.grain.', label: 'Grain', icon: `${BasePath.atmologo}/grain.svg` }, + { prefix: 'social.popfeed.', label: 'Popfeed', icon: `${BasePath.atmologo}/popfeed.svg` } ]; export function collectionIconMatch(collection: string): CollectionIconMatch | null { - return collectionIconMatches.find((match) => collection.startsWith(match.prefix)) ?? null; + return ( + collectionIconMatches + .filter((match) => collection.startsWith(match.prefix)) + .sort((a, b) => b.prefix.length - a.prefix.length)[0] ?? null + ); } export function appLabelForCollection(collection: string): string | null { diff --git a/src/lib/atproto/types.ts b/src/lib/atproto/types.ts index d547dc5..7a8b6f5 100644 --- a/src/lib/atproto/types.ts +++ b/src/lib/atproto/types.ts @@ -46,6 +46,8 @@ export type RecordRouteParams = { did: string; collection: string; rkey: string export type CollectionRouteParams = Pick; +export type CollectionIconMatch = { prefix: string; label: string; icon: string }; + export type UnknownRecord = { uri: string; cid: string; value: unknown }; export function isRecordValue(value: unknown): value is Record { diff --git a/static/icons/namespaces/at/inlay/icon.svg b/static/icons/namespaces/at/inlay/icon.svg new file mode 100644 index 0000000..10074da --- /dev/null +++ b/static/icons/namespaces/at/inlay/icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/static/icons/namespaces/com/atproto/icon.svg b/static/icons/namespaces/com/atproto/icon.svg new file mode 100644 index 0000000..fa1d58c --- /dev/null +++ b/static/icons/namespaces/com/atproto/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/icons/namespaces/com/germnetwork/icon.svg b/static/icons/namespaces/com/germnetwork/icon.svg new file mode 100644 index 0000000..99aaed1 --- /dev/null +++ b/static/icons/namespaces/com/germnetwork/icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/icons/namespaces/computer/aetheros/icon.svg b/static/icons/namespaces/computer/aetheros/icon.svg new file mode 100644 index 0000000..08dcc3e --- /dev/null +++ b/static/icons/namespaces/computer/aetheros/icon.svg @@ -0,0 +1,3 @@ + + +