diff --git a/Makefile b/Makefile index 91247a7a7..e9aecd474 100644 --- a/Makefile +++ b/Makefile @@ -405,7 +405,7 @@ js-lexicons: && sed -i.bak 's/PlaceStreamChatProfile\.Main/PlaceStreamChatProfile\.Record/' $$(find ./js/streamplace/src/lexicons/types/place/stream -type f) \ && sed -i.bak 's/PlaceStreamLivestream\.Main/PlaceStreamLivestream\.Record/' $$(find ./js/streamplace/src/lexicons/types/place/stream/live -type f) \ && for x in $$(find ./js/streamplace/src/lexicons -type f -name '*.ts'); do \ - echo 'import { ComAtprotoSyncGetRepo, AppBskyRichtextFacet, AppBskyGraphBlock, ComAtprotoRepoStrongRef, AppBskyActorDefs, ComAtprotoSyncListRepos, AppBskyActorGetProfile, AppBskyFeedGetFeedSkeleton, ComAtprotoIdentityResolveHandle, ComAtprotoModerationCreateReport, ComAtprotoRepoCreateRecord, ComAtprotoRepoDeleteRecord, ComAtprotoRepoDescribeRepo, ComAtprotoRepoGetRecord, ComAtprotoRepoListRecords, ComAtprotoRepoPutRecord, ComAtprotoRepoUploadBlob, ComAtprotoServerDescribeServer, ComAtprotoSyncGetRecord, ComAtprotoSyncListReposComAtprotoRepoCreateRecord, ComAtprotoRepoDeleteRecord, ComAtprotoRepoGetRecord, ComAtprotoRepoListRecords, ComAtprotoIdentityRefreshIdentity } from "@atproto/api"' >> $$x; \ + echo 'import { ComAtprotoSyncGetRepo, AppBskyRichtextFacet, AppBskyGraphBlock, ComAtprotoRepoStrongRef, AppBskyActorDefs, ComAtprotoSyncListRepos, AppBskyActorGetProfile, AppBskyFeedGetFeedSkeleton, ComAtprotoIdentityResolveHandle, ComAtprotoModerationCreateReport, ComAtprotoRepoCreateRecord, ComAtprotoRepoDeleteRecord, ComAtprotoRepoDescribeRepo, ComAtprotoRepoGetRecord, ComAtprotoRepoListRecords, ComAtprotoRepoPutRecord, ComAtprotoRepoUploadBlob, ComAtprotoServerDescribeServer, ComAtprotoSyncGetRecord, ComAtprotoIdentityRefreshIdentity } from "@atproto/api"' >> $$x; \ done \ && npx prettier --ignore-unknown --write $$(find ./js/streamplace/src/lexicons -type f -name '*.ts') \ && find . | grep bak$$ | xargs rm diff --git a/js/app/components/activity-picker.tsx b/js/app/components/activity-picker.tsx index e6a26bf0d..d80bf9104 100644 --- a/js/app/components/activity-picker.tsx +++ b/js/app/components/activity-picker.tsx @@ -8,6 +8,7 @@ import { useTheme, zero, } from "@streamplace/components"; +import { ACTIVITY_LABELS } from "@streamplace/components/src/lib/metadata-constants"; import { usePDSAgent } from "@streamplace/components/src/streamplace-store/xrpc"; import { Image } from "expo-image"; import { X } from "lucide-react-native"; @@ -22,19 +23,6 @@ import { getDidFromAtUri, getGameCoverUrl } from "../utils/game"; const { p, px, r, layout, borders, gap, flex } = zero; -const ACTIVITY_LABELS: Array<{ - value: PlaceStreamDefs.ActivityLabel["label"]; - display: string; -}> = [ - { value: "just_chatting", display: "Just Chatting" }, - { value: "music", display: "Music" }, - { value: "art", display: "Art" }, - { value: "programming", display: "Programming" }, - { value: "cooking", display: "Cooking" }, - { value: "fitness", display: "Fitness" }, - { value: "sports", display: "Sports" }, -]; - interface GameResult { uri: string; name: string; diff --git a/js/app/components/mobile/bottom-metadata.tsx b/js/app/components/mobile/bottom-metadata.tsx index 248e78ad2..f26bc6047 100644 --- a/js/app/components/mobile/bottom-metadata.tsx +++ b/js/app/components/mobile/bottom-metadata.tsx @@ -2,6 +2,7 @@ import { Button, ContentRights, ContentWarnings, + formatActivity, formatHandle, formatHandleWithAt, hexToRgba, @@ -32,29 +33,6 @@ const { gap, px, py, colors, r, borders } = zero; const ROSE_RING = "rgba(244, 114, 182, 0.32)"; -const ACTIVITY_LABELS: Record = { - just_chatting: "Just Chatting", - music: "Music", - art: "Art", - programming: "Programming", - cooking: "Cooking", - fitness: "Fitness", - sports: "Sports", -}; - -function formatActivity( - activity: (ActivityGame | ActivityLabel) & { $type?: string }, -): string | null { - if (activity.$type === "place.stream.defs#activityGame") { - return (activity as ActivityGame).name ?? null; - } - if (activity.$type === "place.stream.defs#activityLabel") { - const label = (activity as ActivityLabel).label; - return ACTIVITY_LABELS[label] ?? label; - } - return null; -} - const ATMOCO_STREAMS = [ { handle: "stream1.atmosphereconf.org", label: "Great Hall" }, { handle: "stream2.atmosphereconf.org", label: "Performance Theatre" }, diff --git a/js/app/src/screens/home.tsx b/js/app/src/screens/home.tsx index 1903be7a3..38898458a 100644 --- a/js/app/src/screens/home.tsx +++ b/js/app/src/screens/home.tsx @@ -1,4 +1,9 @@ -import { Text, useStreamplaceStore, zero } from "@streamplace/components"; +import { + ACTIVITY_LABEL_DISPLAY, + Text, + useStreamplaceStore, + zero, +} from "@streamplace/components"; import AQLink from "components/aqlink"; import Container from "components/container"; import ErrorBox from "components/error/error"; @@ -14,16 +19,6 @@ import { Platform, View, useWindowDimensions } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { PlaceStreamDefs, PlaceStreamLivestream } from "streamplace"; -const LABEL_DISPLAY: Record = { - just_chatting: "Just Chatting", - music: "Music", - art: "Art", - programming: "Programming", - cooking: "Cooking", - fitness: "Fitness", - sports: "Sports", -}; - function getStreamCategories(record: PlaceStreamLivestream.Record): string[] { const categories: string[] = []; if (record.activity) { @@ -32,7 +27,7 @@ function getStreamCategories(record: PlaceStreamLivestream.Record): string[] { if (game.name) categories.push(game.name); } else if (record.activity.$type === "place.stream.defs#activityLabel") { const label = record.activity as PlaceStreamDefs.ActivityLabel; - categories.push(LABEL_DISPLAY[label.label] ?? label.label); + categories.push(ACTIVITY_LABEL_DISPLAY[label.label] ?? label.label); } } if (record.tags) { diff --git a/js/components/src/index.tsx b/js/components/src/index.tsx index 4e7b9fc34..cb1ca9221 100644 --- a/js/components/src/index.tsx +++ b/js/components/src/index.tsx @@ -72,3 +72,10 @@ export type { AQStorage } from "./storage/storage.shared"; // Content metadata components export * from "./components/content-metadata"; + +// Shared metadata constants +export { + ACTIVITY_LABELS, + ACTIVITY_LABEL_DISPLAY, + formatActivity, +} from "./lib/metadata-constants"; diff --git a/js/components/src/lib/metadata-constants.ts b/js/components/src/lib/metadata-constants.ts index c2e73d46d..f8e5e9baf 100644 --- a/js/components/src/lib/metadata-constants.ts +++ b/js/components/src/lib/metadata-constants.ts @@ -1,4 +1,52 @@ -import { schemas } from "streamplace"; +import { PlaceStreamDefs, schemas } from "streamplace"; + +// "knownValues": [ +// "events", +// "just_chatting", +// "music", +// "art", +// "software_dev", +// "miniatures", +// "makers_crafting", +// "fitness", +// "sports" +// ] + +export const ACTIVITY_LABELS: Array<{ + value: PlaceStreamDefs.ActivityLabel["label"]; + display: string; +}> = [ + { value: "just_chatting", display: "Just Chatting" }, + { value: "music", display: "Music" }, + { value: "art", display: "Art" }, + { value: "software_dev", display: "Software and Game Development" }, + { value: "miniatures", display: "Miniatures and Models" }, + { value: "events", display: "Special Events" }, + { value: "makers_crafting", display: "Makers and Crafting" }, + { value: "cooking", display: "Cooking and Food" }, + { value: "fitness", display: "Fitness and Health" }, + { value: "sports", display: "Sports" }, +]; + +export const ACTIVITY_LABEL_DISPLAY: Record = + Object.fromEntries( + ACTIVITY_LABELS.map(({ value, display }) => [value, display]), + ); + +export function formatActivity( + activity: (PlaceStreamDefs.ActivityGame | PlaceStreamDefs.ActivityLabel) & { + $type?: string; + }, +): string | null { + if (activity.$type === "place.stream.defs#activityGame") { + return (activity as PlaceStreamDefs.ActivityGame).name ?? null; + } + if (activity.$type === "place.stream.defs#activityLabel") { + const label = (activity as PlaceStreamDefs.ActivityLabel).label; + return ACTIVITY_LABEL_DISPLAY[label] ?? label; + } + return null; +} // Content warnings derived from lexicon schema export const CONTENT_WARNINGS = (() => { diff --git a/js/docs/src/content/docs/features/tags-and-activities.md b/js/docs/src/content/docs/features/tags-and-activities.md new file mode 100644 index 000000000..099f45fb8 --- /dev/null +++ b/js/docs/src/content/docs/features/tags-and-activities.md @@ -0,0 +1,33 @@ +--- +title: Tags and Activities +description: Categorize your stream with activities and freeform tags +--- + +Streamplace lets you attach an activity and tags to your livestream. These can help viewers find and understand your content. + +## Activities + +An activity describes what you're streaming. There are two kinds: + +- Games: Selected from the [Cartridge.dev](https://cartridge.dev) database via search +- Other activities: Chosen from a preset list. + +You can pick one activity per stream. When you create or edit a livestream, the activity picker shows two tabs: "Game" and "Other Activity". Searching in the Game tab queries the catalog, and the Other Activity tab shows the preset labels as selectable pills. + +If we don't have an activity that you expect, feel free to [file an issue](https://github.com/streamplace/streamplace/issues/new), and we'll be happy to review and add it. + +## Tags + +Tags are freeform labels you can add to your stream. Some rules: + +- Alphanumeric characters only (a-z, A-Z, 0-9) +- Up to 10 tags per stream +- Be courteous towards others and follow the community guidelines when picking tags + +### Language tags + +The live dashboard has a language picker that manages language tags for you. + +If we don't have your language, please [file an issue](https://github.com/streamplace/streamplace/issues/new) and we'll be happy to review and add it. + +Note that when viewing a stream, language tags display as their native name and not the language code. diff --git a/lexicons/place/stream/defs.json b/lexicons/place/stream/defs.json index ef73b5409..444dc2b6c 100644 --- a/lexicons/place/stream/defs.json +++ b/lexicons/place/stream/defs.json @@ -53,11 +53,14 @@ "label": { "type": "string", "knownValues": [ + "events", "just_chatting", "music", "art", - "programming", + "software_dev", "cooking", + "miniatures", + "makers_crafting", "fitness", "sports" ]