diff --git a/package.json b/package.json index 7b755a8..78d4687 100644 --- a/package.json +++ b/package.json @@ -108,4 +108,4 @@ "typescript": "^4.1.3", "webpack": "4.33.0" } -} \ No newline at end of file +} diff --git a/src/backend-emulator/main.js b/src/backend-emulator/main.js index e053b5b..ba793f0 100644 --- a/src/backend-emulator/main.js +++ b/src/backend-emulator/main.js @@ -63,7 +63,7 @@ const server = http.createServer((req, res) => { } res.statusCode = response.status; loggedStatus = String(response.status); - Object.entries(response.headers).forEach(([k, v]) => res.setHeader(k, v)); + response.headers.forEach((value, key) => res.setHeader(key, value)); res.end(response.body); } catch (e) { console.error(e); diff --git a/src/backend-worker/handlers/get-picker-data.ts b/src/backend-worker/handlers/get-picker-data.ts new file mode 100644 index 0000000..ebd023a --- /dev/null +++ b/src/backend-worker/handlers/get-picker-data.ts @@ -0,0 +1,78 @@ +import { + DiscordUser, + GuildSlug, + PresentableGuild, + SessionData, +} from 'roleypoly/common/types'; +import { respond, withSession } from '../utils/api-tools'; +import { getGuild, getGuildData, getGuildMemberRoles } from '../utils/guild'; + +const fail = respond( + { + error: 'guild not found', + }, + { + status: 404, + } +); + +export const GetPickerData = withSession( + (session?: SessionData) => async (request: Request): Promise => { + const url = new URL(request.url); + const [, , guildID] = url.pathname.split('/'); + + if (!guildID) { + return respond( + { + error: 'missing guild id', + }, + { + status: 400, + } + ); + } + + const { id: userID } = session?.user as DiscordUser; + const guilds = session?.guilds as GuildSlug[]; + + // Save a Discord API request by checking if this user is a member by session first + const checkGuild = guilds.find((guild) => guild.id === guildID); + if (!checkGuild) { + return fail; + } + + const guild = await getGuild(guildID, { + skipCachePull: url.searchParams.has('__no_cache'), + }); + if (!guild) { + return fail; + } + + const memberRolesP = getGuildMemberRoles({ + serverID: guildID, + userID, + }); + + const guildDataP = getGuildData(guildID); + + const [guildData, memberRoles] = await Promise.all([guildDataP, memberRolesP]); + if (!memberRoles) { + return fail; + } + + const presentableGuild: PresentableGuild = { + id: guildID, + guild: checkGuild, + roles: guild.roles, + member: { + roles: memberRoles, + }, + data: guildData, + }; + + return respond(presentableGuild); + }, + { + mustAuthenticate: true, + } +); diff --git a/src/backend-worker/handlers/login-callback.ts b/src/backend-worker/handlers/login-callback.ts index 4291d31..1d321ce 100644 --- a/src/backend-worker/handlers/login-callback.ts +++ b/src/backend-worker/handlers/login-callback.ts @@ -120,11 +120,15 @@ const getGuilds = async (accessToken: string) => { 'Bearer' ); + if (!guilds) { + return []; + } + const guildSlugs = guilds.map((guild) => ({ id: guild.id, name: guild.name, icon: guild.icon, - permissionLevel: parsePermissions(guild.permissions, guild.owner), + permissionLevel: parsePermissions(BigInt(guild.permissions), guild.owner), })); return guildSlugs; diff --git a/src/backend-worker/index.ts b/src/backend-worker/index.ts index c55d9de..963dc71 100644 --- a/src/backend-worker/index.ts +++ b/src/backend-worker/index.ts @@ -1,4 +1,5 @@ import { BotJoin } from './handlers/bot-join'; +import { GetPickerData } from './handlers/get-picker-data'; import { GetSession } from './handlers/get-session'; import { GetSlug } from './handlers/get-slug'; import { LoginBounce } from './handlers/login-bounce'; @@ -16,6 +17,7 @@ router.add('GET', 'login-bounce', LoginBounce); router.add('GET', 'login-callback', LoginCallback); router.add('GET', 'get-session', GetSession); router.add('GET', 'get-slug', GetSlug); +router.add('GET', 'get-picker-data', GetPickerData); router.add('GET', 'x-headers', (request) => { const headers: { [x: string]: string } = {}; diff --git a/src/backend-worker/utils/api-tools.ts b/src/backend-worker/utils/api-tools.ts index 8f849a3..bd125ab 100644 --- a/src/backend-worker/utils/api-tools.ts +++ b/src/backend-worker/utils/api-tools.ts @@ -28,7 +28,7 @@ export const resolveFailures = ( }; export const parsePermissions = ( - permissions: number, + permissions: bigint, owner: boolean = false ): UserGuildPermissions => { if (owner || evaluatePermission(permissions, Permissions.ADMINISTRATOR)) { @@ -56,6 +56,9 @@ export const getSessionID = (request: Request): { type: string; id: string } | n return { type, id }; }; +const userAgent = + 'DiscordBot (https://github.com/roleypoly/roleypoly, git-main) (+https://roleypoly.com)'; + export const discordFetch = async ( url: string, auth: string, @@ -64,8 +67,7 @@ export const discordFetch = async ( const response = await fetch('https://discord.com/api/v8' + url, { headers: { authorization: `${authType} ${auth}`, - 'user-agent': - 'DiscordBot (https://github.com/roleypoly/roleypoly, git-main) (+https://roleypoly.com)', + 'user-agent': userAgent, }, }); @@ -81,12 +83,17 @@ export const cacheLayer = ( keyFactory: (identity: Identity) => string, missHandler: (identity: Identity) => Promise, ttlSeconds?: number -) => async (identity: Identity): Promise => { +) => async ( + identity: Identity, + options: { skipCachePull?: boolean } = {} +): Promise => { const key = keyFactory(identity); - const value = await kv.get(key); - if (value) { - return value; + if (!options.skipCachePull) { + const value = await kv.get(key); + if (value) { + return value; + } } const fallbackValue = await missHandler(identity); diff --git a/src/backend-worker/utils/guild.ts b/src/backend-worker/utils/guild.ts index bd4f16c..40db870 100644 --- a/src/backend-worker/utils/guild.ts +++ b/src/backend-worker/utils/guild.ts @@ -1,7 +1,15 @@ -import { Guild, Member, Role, RoleSafety } from 'roleypoly/common/types'; +import { + Features, + Guild, + GuildData as GuildDataT, + OwnRoleInfo, + Role, + RoleSafety, +} from 'roleypoly/common/types'; +import { evaluatePermission, permissions } from 'roleypoly/common/utils/hasPermission'; import { cacheLayer, discordFetch } from './api-tools'; -import { botToken } from './config'; -import { Guilds } from './kv'; +import { botClientID, botToken } from './config'; +import { GuildData, Guilds } from './kv'; type APIGuild = { // Only relevant stuff @@ -30,24 +38,112 @@ export const getGuild = cacheLayer( return null; } + const botMemberRoles = + (await getGuildMemberRoles({ + serverID: id, + userID: botClientID, + })) || []; + + const highestRolePosition = botMemberRoles.reduce((highest, roleID) => { + const role = guildRaw.roles.find((guildRole) => guildRole.id === roleID); + if (!role) { + return highest; + } + + // If highest is a bigger number, it stays the highest. + if (highest > role.position) { + return highest; + } + + return role.position; + }, guildRaw.roles.length - 1); + + const roles = guildRaw.roles.map((role) => ({ + id: role.id, + name: role.name, + color: role.color, + managed: role.managed, + position: role.position, + permissions: role.permissions, + safety: calculateRoleSafety(role, highestRolePosition), + })); + // Filters the raw guild data into data we actually want - const guild: Guild = { + const guild: Guild & OwnRoleInfo = { id: guildRaw.id, name: guildRaw.name, icon: guildRaw.icon, - roles: guildRaw.roles.map((role) => ({ - ...role, - safety: RoleSafety.SAFE, // TODO: calculate safety - })), + roles, + highestRolePosition, }; return guild; - } + }, + 60 * 60 * 2 // 2 hour TTL ); -export const getGuildMember = async ( - serverID: string, - userID: string -): Promise => { - return {} as any; +type GuildMemberIdentity = { + serverID: string; + userID: string; +}; + +type APIMember = { + // Only relevant stuff, again. + roles: string[]; +}; + +export const getGuildMemberRoles = cacheLayer( + Guilds, + ({ serverID, userID }) => `guilds/${serverID}/members/${userID}`, + async ({ serverID, userID }) => { + const discordMember = await discordFetch( + `/guilds/${serverID}/members/${userID}`, + botToken, + 'Bot' + ); + + if (!discordMember) { + return null; + } + + return discordMember.roles; + }, + 60 * 5 // 5 minute TTL +); + +export const getGuildData = async (id: string): Promise => { + const guildData = await GuildData.get(id); + + if (!guildData) { + return { + id, + message: '', + categories: [], + features: Features.None, + }; + } + + return guildData; +}; + +const calculateRoleSafety = (role: Role | APIRole, highestBotRolePosition: number) => { + let safety = RoleSafety.Safe; + + if (role.managed) { + safety |= RoleSafety.ManagedRole; + } + + if (role.position > highestBotRolePosition) { + safety |= RoleSafety.HigherThanBot; + } + + const permBigInt = BigInt(role.permissions); + if ( + evaluatePermission(permBigInt, permissions.ADMINISTRATOR) || + evaluatePermission(permBigInt, permissions.MANAGE_ROLES) + ) { + safety |= RoleSafety.DangerousPermissions; + } + + return safety; }; diff --git a/src/common/types/Category.ts b/src/common/types/Category.ts index 8debdb7..190b89f 100644 --- a/src/common/types/Category.ts +++ b/src/common/types/Category.ts @@ -1,12 +1,12 @@ export enum CategoryType { - SINGLE = 0, - MULTI, + Single = 0, + Multi, } export type Category = { id: string; name: string; - rolesList: string[]; + roles: string[]; hidden: boolean; type: CategoryType; position: number; diff --git a/src/common/types/Guild.ts b/src/common/types/Guild.ts index 3be9caf..964c153 100644 --- a/src/common/types/Guild.ts +++ b/src/common/types/Guild.ts @@ -9,34 +9,34 @@ export type Guild = { roles: Role[]; }; -export type GuildRoles = { - id: string; - rolesList: Role[]; -}; +export enum Features { + None, + Preview = None, +} export type GuildData = { id: string; message: string; - categoriesList: Category[]; - entitlementsList: string[]; + categories: Category[]; + features: Features; }; export type PresentableGuild = { id: string; - guild: Guild; + guild: GuildSlug; member: Member; data: GuildData; - roles: GuildRoles; + roles: Role[]; }; export type GuildEnumeration = { - guildsList: PresentableGuild[]; + guilds: PresentableGuild[]; }; export enum UserGuildPermissions { User, - Manager, - Admin, + Manager = 1 << 1, + Admin = 1 << 2, } export type GuildSlug = { diff --git a/src/common/types/Role.ts b/src/common/types/Role.ts index 3eae365..35ec4d8 100644 --- a/src/common/types/Role.ts +++ b/src/common/types/Role.ts @@ -1,7 +1,8 @@ export enum RoleSafety { - SAFE = 0, - HIGHERTHANBOT, - DANGEROUSPERMISSIONS, + Safe = 0, + HigherThanBot = 1 << 1, + DangerousPermissions = 1 << 2, + ManagedRole = 1 << 3, } export type Role = { @@ -14,3 +15,7 @@ export type Role = { /** Permissions is should be used as a BigInt, NOT a number. */ permissions: string; }; + +export type OwnRoleInfo = { + highestRolePosition: number; +}; diff --git a/src/common/types/User.ts b/src/common/types/User.ts index c9f1cde..ed0527d 100644 --- a/src/common/types/User.ts +++ b/src/common/types/User.ts @@ -7,10 +7,10 @@ export type DiscordUser = { }; export type Member = { - guildid: string; - rolesList: string[]; - nick: string; - user: DiscordUser; + guildid?: string; + roles: string[]; + nick?: string; + user?: DiscordUser; }; export type RoleypolyUser = { diff --git a/src/common/types/storyData.ts b/src/common/types/storyData.ts index 1ca1a8b..daa24bc 100644 --- a/src/common/types/storyData.ts +++ b/src/common/types/storyData.ts @@ -2,10 +2,10 @@ import { Category, CategoryType, DiscordUser, + Features, Guild, GuildData, GuildEnumeration, - GuildRoles, GuildSlug, Member, Role, @@ -21,7 +21,7 @@ export const roleCategory: Role[] = [ color: 0xffc0cb, position: 1, managed: false, - safety: RoleSafety.SAFE, + safety: RoleSafety.Safe, }, { id: 'bbb', @@ -30,7 +30,7 @@ export const roleCategory: Role[] = [ color: 0xc0ebff, position: 2, managed: false, - safety: RoleSafety.SAFE, + safety: RoleSafety.Safe, }, { id: 'ccc', @@ -39,7 +39,7 @@ export const roleCategory: Role[] = [ color: 0xc0ffd5, position: 3, managed: false, - safety: RoleSafety.SAFE, + safety: RoleSafety.Safe, }, { id: 'ddd', @@ -48,7 +48,7 @@ export const roleCategory: Role[] = [ color: 0xff0000, position: 4, managed: false, - safety: RoleSafety.SAFE, + safety: RoleSafety.Safe, }, { id: 'eee', @@ -57,7 +57,7 @@ export const roleCategory: Role[] = [ color: 0x000000, position: 5, managed: false, - safety: RoleSafety.SAFE, + safety: RoleSafety.Safe, }, { id: 'fff', @@ -66,7 +66,7 @@ export const roleCategory: Role[] = [ color: 0x1, position: 6, managed: false, - safety: RoleSafety.SAFE, + safety: RoleSafety.Safe, }, { id: 'unsafe1', @@ -75,7 +75,7 @@ export const roleCategory: Role[] = [ color: 0xff0088, position: 7, managed: false, - safety: RoleSafety.HIGHERTHANBOT, + safety: RoleSafety.HigherThanBot, }, { id: 'unsafe2', @@ -84,16 +84,16 @@ export const roleCategory: Role[] = [ color: 0x00ff88, position: 8, managed: false, - safety: RoleSafety.DANGEROUSPERMISSIONS, + safety: RoleSafety.DangerousPermissions, }, ]; export const mockCategory: Category = { id: 'aaa', name: 'Mock', - rolesList: roleCategory.map((x) => x.id), + roles: roleCategory.map((x) => x.id), hidden: false, - type: CategoryType.MULTI, + type: CategoryType.Multi, position: 0, }; @@ -105,7 +105,7 @@ export const roleCategory2: Role[] = [ color: 0xff0000, position: 9, managed: false, - safety: RoleSafety.SAFE, + safety: RoleSafety.Safe, }, { id: 'eee2', @@ -114,24 +114,19 @@ export const roleCategory2: Role[] = [ color: 0x00ff00, position: 10, managed: false, - safety: RoleSafety.SAFE, + safety: RoleSafety.Safe, }, ]; export const mockCategorySingle: Category = { id: 'bbb', name: 'Mock Single 岡野', - rolesList: roleCategory2.map((x) => x.id), + roles: roleCategory2.map((x) => x.id), hidden: false, - type: CategoryType.SINGLE, + type: CategoryType.Single, position: 0, }; -export const guildRoles: GuildRoles = { - id: 'aaa', - rolesList: [...roleCategory, ...roleCategory2], -}; - export const roleWikiData = { aaa: 'Typically used by feminine-identifying people', bbb: 'Typically used by masculine-identifying people', @@ -145,33 +140,38 @@ export const guild: Guild = { roles: [], }; -export const guildMap: { [x: string]: Guild } = { - 'emoji megaporium': guild, +export const guildMap: { [x: string]: GuildSlug } = { + 'emoji megaporium': { + name: guild.name, + id: guild.id, + permissionLevel: 0, + icon: guild.icon, + }, Roleypoly: { name: 'Roleypoly', id: '203493697696956418', + permissionLevel: 0, icon: 'ff08d36f5aee1ff48f8377b65d031ab0', - roles: [], }, 'chamber of secrets': { name: 'chamber of secrets', id: 'aaa', + permissionLevel: 0, icon: '', - roles: [], }, Eclipse: { name: 'Eclipse', id: '408821059161423873', + permissionLevel: 0, icon: '49dfdd8b2456e2977e80a8b577b19c0d', - roles: [], }, }; export const guildData: GuildData = { id: 'aaa', message: 'henlo worl!!', - categoriesList: [mockCategory, mockCategorySingle], - entitlementsList: [], + categories: [mockCategory, mockCategorySingle], + features: Features.None, }; export const user: DiscordUser = { @@ -184,7 +184,7 @@ export const user: DiscordUser = { export const member: Member = { guildid: 'aaa', - rolesList: ['aaa', 'eee', 'unsafe2', 'ddd2'], + roles: ['aaa', 'eee', 'unsafe2', 'ddd2'], nick: 'okano cat', user: user, }; @@ -194,42 +194,42 @@ export const rpUser: RoleypolyUser = { }; export const guildEnum: GuildEnumeration = { - guildsList: [ + guilds: [ { id: 'aaa', guild: guildMap['emoji megaporium'], member, data: guildData, - roles: guildRoles, + roles: [...roleCategory, ...roleCategory2], }, { id: 'bbb', guild: guildMap['Roleypoly'], member: { ...member, - rolesList: ['unsafe2'], + roles: ['unsafe2'], }, data: guildData, - roles: guildRoles, + roles: [...roleCategory, ...roleCategory2], }, { id: 'ccc', guild: guildMap['chamber of secrets'], member, data: guildData, - roles: guildRoles, + roles: [...roleCategory, ...roleCategory2], }, { id: 'ddd', guild: guildMap['Eclipse'], member, data: guildData, - roles: guildRoles, + roles: [...roleCategory, ...roleCategory2], }, ], }; -export const mastheadSlugs: GuildSlug[] = guildEnum.guildsList.map( +export const mastheadSlugs: GuildSlug[] = guildEnum.guilds.map( (guild, idx) => ({ id: guild.guild.id, name: guild.guild.name, diff --git a/src/common/utils/hasPermission.spec.ts b/src/common/utils/hasPermission.spec.ts index 3a167cc..9d917b5 100644 --- a/src/common/utils/hasPermission.spec.ts +++ b/src/common/utils/hasPermission.spec.ts @@ -1,20 +1,29 @@ import { Role } from 'roleypoly/common/types'; -import { guildRoles } from 'roleypoly/common/types/storyData'; -import { hasPermission, hasPermissionOrAdmin, permissions } from './hasPermission'; +import { roleCategory } from 'roleypoly/common/types/storyData'; +import { hasPermission, hasPermissionOrAdmin } from './hasPermission'; + +export const permissions = { + KICK_MEMBERS: BigInt(0x2), + BAN_MEMBERS: BigInt(0x4), + ADMINISTRATOR: BigInt(0x8), + SPEAK: BigInt(0x200000), + CHANGE_NICKNAME: BigInt(0x4000000), + MANAGE_ROLES: BigInt(0x10000000), +}; const roles: Role[] = [ { - ...guildRoles.rolesList[0], + ...roleCategory[0], permissions: String(permissions.ADMINISTRATOR), }, { - ...guildRoles.rolesList[0], + ...roleCategory[0], permissions: String( permissions.SPEAK | permissions.BAN_MEMBERS | permissions.CHANGE_NICKNAME ), }, { - ...guildRoles.rolesList[0], + ...roleCategory[0], permissions: String(permissions.BAN_MEMBERS), }, ]; diff --git a/src/common/utils/hasPermission.ts b/src/common/utils/hasPermission.ts index 4eb37c2..081159d 100644 --- a/src/common/utils/hasPermission.ts +++ b/src/common/utils/hasPermission.ts @@ -19,35 +19,37 @@ export const hasPermissionOrAdmin = (roles: Role[], permission: bigint): boolean hasPermission(roles, permission | permissions.ADMINISTRATOR); export const permissions = { - CREATE_INSTANT_INVITE: BigInt(0x1), - KICK_MEMBERS: BigInt(0x2), - BAN_MEMBERS: BigInt(0x4), + // IMPORTANT: Only uncomment what's actually used. All are left for convenience. + + // CREATE_INSTANT_INVITE: BigInt(0x1), + // KICK_MEMBERS: BigInt(0x2), + // BAN_MEMBERS: BigInt(0x4), ADMINISTRATOR: BigInt(0x8), - MANAGE_CHANNELS: BigInt(0x10), - MANAGE_GUILD: BigInt(0x20), - ADD_REACTIONS: BigInt(0x40), - VIEW_AUDIT_LOG: BigInt(0x80), - VIEW_CHANNEL: BigInt(0x400), - SEND_MESSAGES: BigInt(0x800), - SEND_TTS_MESSAGES: BigInt(0x1000), - MANAGE_MESSAGES: BigInt(0x2000), - EMBED_LINKS: BigInt(0x4000), - ATTACH_FILES: BigInt(0x8000), - READ_MESSAGE_HISTORY: BigInt(0x10000), - MENTION_EVERYONE: BigInt(0x20000), - USE_EXTERNAL_EMOJIS: BigInt(0x40000), - VIEW_GUILD_INSIGHTS: BigInt(0x80000), - CONNECT: BigInt(0x100000), - SPEAK: BigInt(0x200000), - MUTE_MEMBERS: BigInt(0x400000), - DEAFEN_MEMBERS: BigInt(0x800000), - MOVE_MEMBERS: BigInt(0x1000000), - USE_VAD: BigInt(0x2000000), - PRIORITY_SPEAKER: BigInt(0x100), - STREAM: BigInt(0x200), - CHANGE_NICKNAME: BigInt(0x4000000), - MANAGE_NICKNAMES: BigInt(0x8000000), + // MANAGE_CHANNELS: BigInt(0x10), + // MANAGE_GUILD: BigInt(0x20), + // ADD_REACTIONS: BigInt(0x40), + // VIEW_AUDIT_LOG: BigInt(0x80), + // VIEW_CHANNEL: BigInt(0x400), + // SEND_MESSAGES: BigInt(0x800), + // SEND_TTS_MESSAGES: BigInt(0x1000), + // MANAGE_MESSAGES: BigInt(0x2000), + // EMBED_LINKS: BigInt(0x4000), + // ATTACH_FILES: BigInt(0x8000), + // READ_MESSAGE_HISTORY: BigInt(0x10000), + // MENTION_EVERYONE: BigInt(0x20000), + // USE_EXTERNAL_EMOJIS: BigInt(0x40000), + // VIEW_GUILD_INSIGHTS: BigInt(0x80000), + // CONNECT: BigInt(0x100000), + // SPEAK: BigInt(0x200000), + // MUTE_MEMBERS: BigInt(0x400000), + // DEAFEN_MEMBERS: BigInt(0x800000), + // MOVE_MEMBERS: BigInt(0x1000000), + // USE_VAD: BigInt(0x2000000), + // PRIORITY_SPEAKER: BigInt(0x100), + // STREAM: BigInt(0x200), + // CHANGE_NICKNAME: BigInt(0x4000000), + // MANAGE_NICKNAMES: BigInt(0x8000000), MANAGE_ROLES: BigInt(0x10000000), - MANAGE_WEBHOOKS: BigInt(0x20000000), - MANAGE_EMOJIS: BigInt(0x40000000), + // MANAGE_WEBHOOKS: BigInt(0x20000000), + // MANAGE_EMOJIS: BigInt(0x40000000), }; diff --git a/src/design-system/atoms/role/Role.tsx b/src/design-system/atoms/role/Role.tsx index dedc438..ec18c75 100644 --- a/src/design-system/atoms/role/Role.tsx +++ b/src/design-system/atoms/role/Role.tsx @@ -65,9 +65,9 @@ export const Role = (props: Props) => { const disabledReason = (role: RPCRole) => { switch (role.safety) { - case RoleSafety.HIGHERTHANBOT: + case RoleSafety.HigherThanBot: return `This role is above Roleypoly's own role.`; - case RoleSafety.DANGEROUSPERMISSIONS: + case RoleSafety.DangerousPermissions: const rolePermissions = BigInt(role.permissions); let permissionHits: string[] = []; diff --git a/src/design-system/molecules/editor-category/EditorCategory.tsx b/src/design-system/molecules/editor-category/EditorCategory.tsx index d63cb34..06ac030 100644 --- a/src/design-system/molecules/editor-category/EditorCategory.tsx +++ b/src/design-system/molecules/editor-category/EditorCategory.tsx @@ -19,7 +19,7 @@ type Props = { }; const typeEnumToSwitch = (typeData: CategoryType) => { - if (typeData === CategoryType.SINGLE) { + if (typeData === CategoryType.Single) { return 'Single'; } else { return 'Multiple'; @@ -28,9 +28,9 @@ const typeEnumToSwitch = (typeData: CategoryType) => { const switchToTypeEnum = (typeData: 'Single' | 'Multiple') => { if (typeData === 'Single') { - return CategoryType.SINGLE; + return CategoryType.Single; } else { - return CategoryType.MULTI; + return CategoryType.Multi; } }; @@ -53,14 +53,14 @@ export const EditorCategory = (props: Props) => { updateSearchTerm(''); props.onChange({ ...props.category, - rolesList: [...props.category.rolesList, role.id], + roles: [...props.category.roles, role.id], }); }; const handleRoleDeselect = (role: RoleType) => () => { props.onChange({ ...props.category, - rolesList: props.category.rolesList.filter((x) => x !== role.id), + roles: props.category.roles.filter((x) => x !== role.id), }); }; @@ -122,7 +122,7 @@ export const EditorCategory = (props: Props) => { onChange={(x) => updateSearchTerm(x.target.value)} /> - {props.category.rolesList.map((id) => { + {props.category.roles.map((id) => { const role = props.guildRoles.find((x) => x.id === id); if (!role) { return <>; diff --git a/src/design-system/molecules/picker-category/PickerCategory.tsx b/src/design-system/molecules/picker-category/PickerCategory.tsx index 4932679..3457170 100644 --- a/src/design-system/molecules/picker-category/PickerCategory.tsx +++ b/src/design-system/molecules/picker-category/PickerCategory.tsx @@ -56,7 +56,7 @@ export const PickerCategory = (props: CategoryProps) => ( role={role} selected={props.selectedRoles.includes(role.id)} onClick={props.onChange(role)} - disabled={role.safety !== RoleSafety.SAFE} + disabled={role.safety !== RoleSafety.Safe} tooltipId={props.category.id} /> diff --git a/src/design-system/organisms/editor/EditorShell.tsx b/src/design-system/organisms/editor/EditorShell.tsx index 46cb512..e527b6a 100644 --- a/src/design-system/organisms/editor/EditorShell.tsx +++ b/src/design-system/organisms/editor/EditorShell.tsx @@ -17,12 +17,12 @@ export const EditorShell = (props: Props) => ( const RolesTab = (props: Props) => (
- {props.guild.data.categoriesList.map((category, idx) => ( + {props.guild.data.categories.map((category, idx) => ( console.log(x)} /> diff --git a/src/design-system/organisms/role-picker/RolePicker.spec.tsx b/src/design-system/organisms/role-picker/RolePicker.spec.tsx index f0a3c63..2775415 100644 --- a/src/design-system/organisms/role-picker/RolePicker.spec.tsx +++ b/src/design-system/organisms/role-picker/RolePicker.spec.tsx @@ -8,9 +8,10 @@ import * as React from 'react'; import { guild, guildData, - guildRoles, member, mockCategorySingle, + roleCategory, + roleCategory2, } from 'roleypoly/common/types/storyData'; import { Role } from 'roleypoly/design-system/atoms/role'; import { PickerCategory } from 'roleypoly/design-system/molecules/picker-category'; @@ -19,9 +20,9 @@ import { RolePicker, RolePickerProps } from './RolePicker'; it('unselects the rest of a category in single mode', () => { const props: RolePickerProps = { - guildData: { ...guildData, categoriesList: [mockCategorySingle] }, - member: { ...member, rolesList: [] }, - roles: guildRoles, + guildData: { ...guildData, categories: [mockCategorySingle] }, + member: { ...member, roles: [] }, + roles: [...roleCategory, ...roleCategory2], guild: guild, onSubmit: jest.fn(), editable: false, @@ -34,10 +35,10 @@ it('unselects the rest of a category in single mode', () => { roles.first().props().onClick?.(true); view.find(ResetSubmit).props().onSubmit(); - expect(props.onSubmit).toBeCalledWith([mockCategorySingle.rolesList[0]]); + expect(props.onSubmit).toBeCalledWith([mockCategorySingle.roles[0]]); - roles.last().props().onClick?.(true); + roles.at(1).props().onClick?.(true); view.find(ResetSubmit).props().onSubmit(); - expect(props.onSubmit).toBeCalledWith([mockCategorySingle.rolesList[1]]); + expect(props.onSubmit).toBeCalledWith([mockCategorySingle.roles[1]]); }); diff --git a/src/design-system/organisms/role-picker/RolePicker.stories.tsx b/src/design-system/organisms/role-picker/RolePicker.stories.tsx index fa464e2..bf95622 100644 --- a/src/design-system/organisms/role-picker/RolePicker.stories.tsx +++ b/src/design-system/organisms/role-picker/RolePicker.stories.tsx @@ -1,12 +1,18 @@ import * as React from 'react'; -import { guild, guildData, guildRoles, member } from 'roleypoly/common/types/storyData'; +import { + guild, + guildData, + member, + roleCategory, + roleCategory2, +} from 'roleypoly/common/types/storyData'; import { RolePicker, RolePickerProps } from './RolePicker'; const props: Partial = { guildData: guildData, member: member, guild: guild, - roles: guildRoles, + roles: [...roleCategory, ...roleCategory2], editable: false, }; diff --git a/src/design-system/organisms/role-picker/RolePicker.tsx b/src/design-system/organisms/role-picker/RolePicker.tsx index 08e4469..78f7b00 100644 --- a/src/design-system/organisms/role-picker/RolePicker.tsx +++ b/src/design-system/organisms/role-picker/RolePicker.tsx @@ -5,7 +5,6 @@ import { CategoryType, Guild, GuildData, - GuildRoles, Member, Role, } from 'roleypoly/common/types'; @@ -27,7 +26,7 @@ export type RolePickerProps = { guild: Guild; guildData: GuildData; member: Member; - roles: GuildRoles; + roles: Role[]; onSubmit: (selectedRoles: string[]) => void; editable: boolean; }; @@ -43,15 +42,15 @@ const arrayMatches = (a: any[], b: any[]) => { export const RolePicker = (props: RolePickerProps) => { const [selectedRoles, updateSelectedRoles] = React.useState( - props.member.rolesList + props.member.roles ); const handleChange = (category: Category) => (role: Role) => (newState: boolean) => { - if (category.type === CategoryType.SINGLE) { + if (category.type === CategoryType.Single) { updateSelectedRoles( newState === true ? [ - ...selectedRoles.filter((x) => !category.rolesList.includes(x)), + ...selectedRoles.filter((x) => !category.roles.includes(x)), role.id, ] : selectedRoles.filter((x) => x !== role.id) @@ -79,31 +78,29 @@ export const RolePicker = (props: RolePickerProps) => { )} - {props.guildData.categoriesList.length !== 0 ? ( + {props.guildData.categories.length !== 0 ? ( <>
- {props.guildData.categoriesList.map((category, idx) => ( + {props.guildData.categories.map((category, idx) => ( - category.rolesList.includes(roleId) + category.roles.includes(roleId) )} roles={ - category.rolesList + category.roles .map((role) => - props.roles.rolesList.find( - (r) => r.id === role - ) + props.roles.find((r) => r.id === role) ) .filter((r) => r !== undefined) as Role[] } onChange={handleChange(category)} wikiMode={false} type={ - category.type === CategoryType.SINGLE + category.type === CategoryType.Single ? 'single' : 'multi' } @@ -112,12 +109,12 @@ export const RolePicker = (props: RolePickerProps) => { ))}
props.onSubmit(selectedRoles)} onReset={() => { - updateSelectedRoles(props.member.rolesList); + updateSelectedRoles(props.member.roles); }} /> diff --git a/src/design-system/templates/role-picker/RolePicker.stories.tsx b/src/design-system/templates/role-picker/RolePicker.stories.tsx index f7f8dd7..03efc97 100644 --- a/src/design-system/templates/role-picker/RolePicker.stories.tsx +++ b/src/design-system/templates/role-picker/RolePicker.stories.tsx @@ -3,9 +3,10 @@ import { guild, guildData, guildEnum, - guildRoles, mastheadSlugs, member, + roleCategory, + roleCategory2, user, } from 'roleypoly/common/types/storyData'; import { RolePickerTemplate, RolePickerTemplateProps } from './RolePicker'; @@ -19,7 +20,7 @@ const props: RolePickerTemplateProps = { member: member, guild: guild, guilds: mastheadSlugs, - roles: guildRoles, + roles: [...roleCategory, ...roleCategory2], editable: false, user: user, guildEnumeration: guildEnum,