Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions apps/juxtaposition-ui/src/models/communities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ enum COMMUNITY_TYPE {
Private = 3
}

export interface IIconPaths {
32: string;
48: string;
64: string;
96: string;
128: string;
}

export interface ICommunityPermissions {
open: boolean;
minimum_new_post_access_level: number;
Expand Down Expand Up @@ -38,6 +46,7 @@ export interface ICommunity {
icon: string;
ctr_header?: string;
wup_header?: string;
icon_paths?: IIconPaths;
/** @deprecated Does not actually exist on any community. Use title_id */
title_ids?: string[]; // Does not exist on any community
title_id: string[];
Expand Down Expand Up @@ -102,6 +111,29 @@ export const PermissionsSchema = new Schema<ICommunityPermissions>({
}
});

const IconPathsSchema = new Schema<IIconPaths>({
32: {
type: String,
required: true
},
48: {
type: String,
required: true
},
64: {
type: String,
required: true
},
96: {
type: String,
required: true
},
128: {
type: String,
required: true
}
});

/* Constraints here (default, required etc.) apply to new documents being added
* See ICommunity for expected shape of query results
* If you add default: or required:, please also update ICommunity and ICommunityInput!
Expand Down Expand Up @@ -171,6 +203,7 @@ export const CommunitySchema = new Schema<ICommunity, CommunityModel, ICommunity
},
ctr_header: { type: String },
wup_header: { type: String },
icon_paths: { type: IconPathsSchema },
title_ids: {
type: [String],
default: undefined
Expand Down
45 changes: 32 additions & 13 deletions apps/juxtaposition-ui/src/services/juxt-web/routes/admin/admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { WebNewCommunityView } from '@/services/juxt-web/views/web/admin/newComm
import { WebEditCommunityView } from '@/services/juxt-web/views/web/admin/editCommunityView';
import { WebModerateUserView } from '@/services/juxt-web/views/web/admin/moderateUserView';
import { zodCommaSeperatedList } from '@/services/juxt-web/routes/schemas';
import type { ICommunityInput, IIconPaths } from '@/models/communities';
import type { ReportWithPost } from '@/services/juxt-web/views/web/admin/reportListView';
import type { HydratedSettingsDocument } from '@/models/settings';
import type { HydratedReportDocument } from '@/models/report';
Expand Down Expand Up @@ -452,8 +453,15 @@ adminRouter.post('/communities/new', upload.fields([{ name: 'browserIcon', maxCo
return res.sendStatus(422);
}

const document = {
platform_id: body.platform,
const iconPaths: IIconPaths = {
32: icons.icon32,
48: icons.icon48,
64: icons.icon64,
96: icons.icon96,
128: icons.icon128
};
const document: ICommunityInput = {
platform_id: Number(body.platform),
name: body.name,
description: body.description,
open: true,
Expand All @@ -468,6 +476,7 @@ adminRouter.post('/communities/new', upload.fields([{ name: 'browserIcon', maxCo
icon: icons.tgaBlob,
ctr_header: headers.ctr,
wup_header: headers.wup,
icon_paths: iconPaths,
title_id: body.title_ids,
community_id: communityId,
olive_community_id: communityId,
Expand All @@ -482,19 +491,19 @@ adminRouter.post('/communities/new', upload.fields([{ name: 'browserIcon', maxCo

updateCommunityHash(newCommunity);

const communityType = getCommunityType(document.type);
const communityPlatform = getCommunityPlatform(document.platform_id);
const communityType = getCommunityType(newCommunity.type);
const communityPlatform = getCommunityPlatform(newCommunity.platform_id);
const changes = [];

changes.push(`Name set to "${document.name}"`);
changes.push(`Description set to "${document.description}"`);
changes.push(`Name set to "${newCommunity.name}"`);
changes.push(`Description set to "${newCommunity.description}"`);
changes.push(`Platform ID set to "${communityPlatform}"`);
changes.push(`Type set to "${communityType}"`);
changes.push(`Title IDs set to "${document.title_id.join(', ')}"`);
changes.push(`Parent set to "${document.parent}"`);
changes.push(`App data set to "${document.app_data}"`);
changes.push(`Is Recommended set to "${document.is_recommended}"`);
changes.push(`Has Shop Page set to "${document.has_shop_page}"`);
changes.push(`Title IDs set to "${newCommunity.title_id.join(', ')}"`);
changes.push(`Parent set to "${newCommunity.parent}"`);
changes.push(`App data set to "${newCommunity.app_data}"`);
changes.push(`Is Recommended set to "${newCommunity.is_recommended}"`);
changes.push(`Has Shop Page set to "${newCommunity.has_shop_page}"`);

const fields = [
'name',
Expand Down Expand Up @@ -602,11 +611,21 @@ adminRouter.post('/communities/:id', upload.fields([{ name: 'browserIcon', maxCo
}
}

const document = {
const iconPaths: IIconPaths | undefined = icons
? {
32: icons.icon32,
48: icons.icon48,
64: icons.icon64,
96: icons.icon96,
128: icons.icon128
}
: undefined;
const document: Partial<ICommunityInput> = {
type: body.type,
has_shop_page: body.has_shop_page,
platform_id: body.platform,
platform_id: Number(body.platform),
icon: icons?.tgaBlob ?? oldCommunity.icon,
icon_paths: iconPaths,
ctr_header: headers?.ctr ?? oldCommunity.ctr_header,
wup_header: headers?.wup ?? oldCommunity.wup_header,
title_id: body.title_ids,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { CtrPageBody, CtrRoot } from '@/services/juxt-web/views/ctr/root';
import { useUrl } from '@/services/juxt-web/views/common/hooks/useUrl';
import { T } from '@/services/juxt-web/views/common/components/T';
import { CtrCommunityIcon } from '@/services/juxt-web/views/ctr/components/ui/CtrCommunityIcon';
import type { ReactNode } from 'react';
import type { CommunityItemProps, CommunityListViewProps, CommunityOverviewViewProps } from '@/services/juxt-web/views/web/communityListView';

export function CtrCommunityItem(props: CommunityItemProps): ReactNode {
const url = useUrl();
const id = props.community.olive_community_id;
return (
<li id={id}>
<a href={`/titles/${id}/new`} data-pjax="#body" className="scroll to-community-button">
<span className="icon-container"><img src={url.cdn(`/icons/${id}/64.png`)} className="icon" alt="" /></span>
<CtrCommunityIcon community={props.community} size="64"></CtrCommunityIcon>
<div className="body">
<div className="body-content">
<span className="community-name title">{props.community.name}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { CtrPageBody, CtrRoot } from '@/services/juxt-web/views/ctr/root';
import { CtrPostListClosedView } from '@/services/juxt-web/views/ctr/postList';
import { useUrl } from '@/services/juxt-web/views/common/hooks/useUrl';
import { T } from '@/services/juxt-web/views/common/components/T';
import { CtrCommunityIcon } from '@/services/juxt-web/views/ctr/components/ui/CtrCommunityIcon';
import { CtrNavTab, CtrNavTabs, CtrNavTabsRow } from '@/services/juxt-web/views/ctr/components/ui/CtrNavTabs';
import type { ReactNode } from 'react';
import type { CommunityViewProps } from '@/services/juxt-web/views/web/communityView';

export function CtrCommunityView(props: CommunityViewProps): ReactNode {
const url = useUrl();
const community = props.community;
const { bannerUrl, imageId, legacy } = url.ctrHeader(community);
const { bannerUrl, legacy } = url.ctrHeader(community);

return (
<CtrRoot title={community.name}>
Expand All @@ -29,9 +30,7 @@ export function CtrCommunityView(props: CommunityViewProps): ReactNode {
>
<h1 id="page-title" className="community">
<span>
<span className="icon-container">
<img src={url.cdn(`/icons/${imageId}/64.png`)} className="icon" />
</span>
<CtrCommunityIcon community={community} size="64"></CtrCommunityIcon>
<span className="community-name">
{community.name}
</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CtrIcon } from '@/services/juxt-web/views/ctr/components/ui/CtrIcon';
import { useUrl } from '@/services/juxt-web/views/common/hooks/useUrl';
import type { ReactNode } from 'react';
import type { CommunityIconProps } from '@/services/juxt-web/views/web/components/ui/WebCommunityIcon';

export function CtrCommunityIcon(props: CommunityIconProps): ReactNode {
const url = useUrl();
const imageId = props.community.parent ? props.community.parent : props.community.olive_community_id;
const iconUrl = props.community.icon_paths ? url.cdn(props.community.icon_paths[props.size]) : url.cdn(`/icons/${imageId}/${props.size}.png`);
const href = `/communities/${props.community.community_id}`;

return (
<CtrIcon
href={href}
src={iconUrl}
type="icon"
className={props.className}
>
</CtrIcon>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type { MiiIconProps } from '@/services/juxt-web/views/web/components/ui/W

export function CtrMiiIcon(props: MiiIconProps): ReactNode {
const url = useUrl();

const miiUrl = props.face_url ?? url.cdn(`/mii/${props.pid}/normal_face.png`);
const href = `/users/${props.pid}`;
const type = props.type ?? 'mii-icon';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { PortalPageBody, PortalRoot } from '@/services/juxt-web/views/portal/root';
import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar';
import { useUrl } from '@/services/juxt-web/views/common/hooks/useUrl';
import { T } from '@/services/juxt-web/views/common/components/T';
import { PortalCommunityIcon } from '@/services/juxt-web/views/portal/components/ui/PortalCommunityIcon';
import type { ReactNode } from 'react';
import type { CommunityItemProps, CommunityListViewProps, CommunityOverviewViewProps } from '@/services/juxt-web/views/web/communityListView';

export function PortalCommunityItem(props: CommunityItemProps): ReactNode {
const url = useUrl();
const id = props.community.olive_community_id;
const imageCommunityId = props.community.parent ? props.community.parent : id;
return (
<li id={id}>
<span className="icon-container"><img src={url.cdn(`/icons/${imageCommunityId}/128.png`)} className="icon" alt="" /></span>
<PortalCommunityIcon community={props.community} size="128" />
<a href={`/titles/${id}/new`} data-pjax="#body" className="scroll to-community-button"></a>
<div className="body">
<div className="body-content">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar';
import { PortalPostListClosedView } from '@/services/juxt-web/views/portal/postList';
import { useUrl } from '@/services/juxt-web/views/common/hooks/useUrl';
import { T } from '@/services/juxt-web/views/common/components/T';
import { PortalCommunityIcon } from '@/services/juxt-web/views/portal/components/ui/PortalCommunityIcon';
import { PortalUIIcon } from '@/services/juxt-web/views/portal/components/ui/PortalUIIcon';
import { PortalNavTab, PortalNavTabs, PortalNavTabsRow } from '@/services/juxt-web/views/portal/components/ui/PortalNavTabs';
import type { ReactNode } from 'react';
Expand Down Expand Up @@ -48,12 +49,7 @@ export function PortalCommunityView(props: CommunityViewProps): ReactNode {
</div>

<div className="community-info info-content with-header-banner">
<span className="icon-container">
<img
src={url.cdn(`/icons/${imageId}/128.png`)}
className="icon"
/>
</span>
<PortalCommunityIcon community={community} size="128"></PortalCommunityIcon>
{community.permissions.open
? (
<a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PortalIcon } from '@/services/juxt-web/views/portal/components/ui/PortalIcon';
import { useUrl } from '@/services/juxt-web/views/common/hooks/useUrl';
import type { ReactNode } from 'react';
import type { CommunityIconProps } from '@/services/juxt-web/views/web/components/ui/WebCommunityIcon';

export function PortalCommunityIcon(props: CommunityIconProps): ReactNode {
const url = useUrl();
const imageId = props.community.parent ? props.community.parent : props.community.olive_community_id;
const iconUrl = props.community.icon_paths ? url.cdn(props.community.icon_paths[props.size]) : url.cdn(`/icons/${imageId}/${props.size}.png`);
const href = `/communities/${props.community.community_id}`;

return (
<PortalIcon
href={href}
src={iconUrl}
type="icon"
className={props.className}
>
</PortalIcon>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ export function WebEditCommunityView(props: EditCommunityViewProps): ReactNode {
<input type="file" id="browserIcon" data-image-preview accept="image/jpg" name="browserIcon" />
</div>
<div className="col-md-3">
<img src={url.cdn(`/icons/${imageId}/128.png`)} data-image-preview-for="browserIcon" id="browserIconPreview" />
{community.icon_paths
? (
<img src={url.cdn(community.icon_paths['128'])} data-image-preview-for="browserIcon" id="browserIconPreview" />
)
: (
<img src={url.cdn(`/icons/${imageId}/128.png`)} data-image-preview-for="browserIcon" id="browserIconPreview" />
)}
</div>
<div className="col-md-3">
<label className="labels" htmlFor="CTRbrowserHeader">3DS Browser Banner (400px x 220px)</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { WebIcon } from '@/services/juxt-web/views/web/components/ui/WebIcon';
import { useUrl } from '@/services/juxt-web/views/common/hooks/useUrl';
import type { ReactNode } from 'react';
import type { InferSchemaType } from 'mongoose';
import type { CommunitySchema } from '@/models/communities';

export type CommunityIconProps = {
community: InferSchemaType<typeof CommunitySchema>;
size: '32' | '48' | '64' | '96' | '128';
className?: string;
};

export function WebCommunityIcon(props: CommunityIconProps): ReactNode {
const url = useUrl();
const imageId = props.community.parent ? props.community.parent : props.community.olive_community_id;
const iconUrl = props.community.icon_paths ? url.cdn(props.community.icon_paths[props.size]) : url.cdn(`/icons/${imageId}/${props.size}.png`);
const href = `/communities/${props.community.community_id}`;

return (
<WebIcon
href={href}
src={iconUrl}
type="icon"
className={props.className}
>
</WebIcon>
);
}
26 changes: 25 additions & 1 deletion apps/miiverse-api/src/models/community.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import crypto from 'node:crypto';
import { Schema, model } from 'mongoose';
import { MongoError } from 'mongodb';
import { CommunityShotModes } from '@/types/mongoose/community';
import type { ICommunity, ICommunityMethods, CommunityModel, ICommunityPermissions, HydratedCommunityDocument, ICommunityInput } from '@/types/mongoose/community';
import type { ICommunity, ICommunityMethods, CommunityModel, ICommunityPermissions, HydratedCommunityDocument, ICommunityInput, IIconPaths } from '@/types/mongoose/community';
import type { CommunityData } from '@/types/miiverse/community';

const PermissionsSchema = new Schema<ICommunityPermissions>({
Expand All @@ -24,6 +24,29 @@ const PermissionsSchema = new Schema<ICommunityPermissions>({
}
});

const IconPathsSchema = new Schema<IIconPaths>({
32: {
type: String,
required: true
},
48: {
type: String,
required: true
},
64: {
type: String,
required: true
},
96: {
type: String,
required: true
},
128: {
type: String,
required: true
}
});

/* Constraints here (default, required etc.) apply to new documents being added
* See ICommunity for expected shape of query results
* If you add default: or required:, please also update ICommunity and ICommunityInput!
Expand Down Expand Up @@ -91,6 +114,7 @@ const CommunitySchema = new Schema<ICommunity, CommunityModel, ICommunityMethods
},
ctr_header: { type: String },
wup_header: { type: String },
icon_paths: { type: IconPathsSchema },
title_ids: {
type: [String],
default: undefined
Expand Down
Loading