diff --git a/components/cards/selectCardContent.tsx b/components/cards/selectCardContent.tsx index e9fdce4e9..00e9ea994 100644 --- a/components/cards/selectCardContent.tsx +++ b/components/cards/selectCardContent.tsx @@ -36,6 +36,13 @@ export default function SelectCardContent({ option }: SelectCardContentProps) { {!option.hideStats && option.stats &&
{option.stats.getText()}
} + {option.level?.isRanked && <> +
+ + + +
+ } {option.stats?.isSolved() &&
diff --git a/components/formatted/formattedUser.tsx b/components/formatted/formattedUser.tsx index f7abb48ca..222b23f26 100644 --- a/components/formatted/formattedUser.tsx +++ b/components/formatted/formattedUser.tsx @@ -97,6 +97,10 @@ export default function FormattedUser({ className, id, noLinks, noTooltip, onCli user={user} />
+
+ Ranked Solves: + {userExtendedData.user.calcRankedSolves} +
Levels Solved: {userExtendedData.user.score} diff --git a/components/level/info/formattedLevelInfo.tsx b/components/level/info/formattedLevelInfo.tsx index 37aa8a60e..c1c743c6c 100644 --- a/components/level/info/formattedLevelInfo.tsx +++ b/components/level/info/formattedLevelInfo.tsx @@ -2,7 +2,9 @@ import { Tab } from '@headlessui/react'; import FormattedDate from '@root/components/formatted/formattedDate'; import Solved from '@root/components/level/info/solved'; import FormattedLevelReviews from '@root/components/level/reviews/formattedLevelReviews'; +import StyledTooltip from '@root/components/page/styledTooltip'; import Image from 'next/image'; +import Link from 'next/link'; import React, { useContext, useState } from 'react'; import Dimensions from '../../../constants/dimensions'; import { AppContext } from '../../../contexts/appContext'; @@ -84,6 +86,29 @@ export default function FormattedLevelInfo({ level }: FormattedLevelInfoProps) { return (<>
+ {level.isRanked && <> + + + + + {/* + + */} + + Ranked + + + } + {/* {level.isRanked && +
+ Ranked +
+ } */}
{level.name}
diff --git a/components/tables/dataTable.tsx b/components/tables/dataTable.tsx index 5b689719c..553d12fc0 100644 --- a/components/tables/dataTable.tsx +++ b/components/tables/dataTable.tsx @@ -3,7 +3,7 @@ import React from 'react'; export interface TableColumn { id: string; - name: React.ReactNode; + name?: React.ReactNode; selector: (row: T) => JSX.Element; sortable?: boolean; style?: React.CSSProperties | undefined; diff --git a/models/db/level.d.ts b/models/db/level.d.ts index 584c25983..c6433c011 100644 --- a/models/db/level.d.ts +++ b/models/db/level.d.ts @@ -18,6 +18,7 @@ interface Level { height: number; isDeleted: boolean; isDraft: boolean; + isRanked: boolean; leastMoves: number; name: string; slug: string; diff --git a/models/db/user.d.ts b/models/db/user.d.ts index 29f389fed..f054dd915 100644 --- a/models/db/user.d.ts +++ b/models/db/user.d.ts @@ -8,6 +8,7 @@ interface User { _id: Types.ObjectId; avatarUpdatedAt?: number; bio?: string; + calcRankedSolves: number; calc_levels_created_count: number; calc_records: number; chapterUnlocked?: number; // chapter unlocked in the campaign diff --git a/models/schemas/levelSchema.ts b/models/schemas/levelSchema.ts index dc0c79f94..3cfa5b32b 100644 --- a/models/schemas/levelSchema.ts +++ b/models/schemas/levelSchema.ts @@ -7,7 +7,7 @@ import { AttemptContext } from './playAttemptSchema'; export const LEVEL_DEFAULT_PROJECTION = { _id: 1, name: 1, slug: 1, width: 1, height: 1, data: 1, leastMoves: 1, calc_difficulty_estimate: 1, userId: 1, calc_playattempts_unique_users_count: { $size: '$calc_playattempts_unique_users' }, }; // adds ts,calc_reviews_score_laplace and users solved -export const LEVEL_SEARCH_DEFAULT_PROJECTION = { _id: 1, ts: 1, name: 1, slug: 1, /*width: 1, height: 1, data: 1,*/ leastMoves: 1, calc_difficulty_estimate: 1, userId: 1, calc_playattempts_unique_users_count: { $size: '$calc_playattempts_unique_users' }, calc_reviews_score_laplace: 1, calc_stats_players_beaten: 1, calc_reviews_count: 1 }; +export const LEVEL_SEARCH_DEFAULT_PROJECTION = { _id: 1, ts: 1, name: 1, slug: 1, /*width: 1, height: 1, data: 1,*/ leastMoves: 1, calc_difficulty_estimate: 1, userId: 1, calc_playattempts_unique_users_count: { $size: '$calc_playattempts_unique_users' }, calc_reviews_score_laplace: 1, calc_stats_players_beaten: 1, calc_reviews_count: 1, isRanked: 1 }; const LevelSchema = new mongoose.Schema( { @@ -78,6 +78,10 @@ const LevelSchema = new mongoose.Schema( type: Boolean, required: true, }, + isRanked: { + type: Boolean, + required: true, + }, leastMoves: { type: Number, required: true, diff --git a/models/schemas/userSchema.ts b/models/schemas/userSchema.ts index cad3e0c78..238d73a85 100644 --- a/models/schemas/userSchema.ts +++ b/models/schemas/userSchema.ts @@ -27,6 +27,11 @@ const UserSchema = new mongoose.Schema({ maxlength: 256, select: false }, + calcRankedSolves: { + type: Number, + required: true, + default: 0, + }, calc_levels_created_count: { type: Number, default: 0, @@ -101,6 +106,7 @@ const UserSchema = new mongoose.Schema({ }, }); +UserSchema.index({ calcRankedSolves: -1 }); UserSchema.index({ score: -1 }); UserSchema.index({ name: 1 }, { unique: true }); UserSchema.index({ email: 1 }, { unique: true }); diff --git a/pages/api/collection-by-id/[id].ts b/pages/api/collection-by-id/[id].ts index ca47281a5..185dd14ac 100644 --- a/pages/api/collection-by-id/[id].ts +++ b/pages/api/collection-by-id/[id].ts @@ -143,6 +143,7 @@ export async function getCollections({ matchQuery, reqUser, includeDraft, popula { $project: { leastMoves: 1, + isRanked: 1, ...(includeDraft ? { isDraft: 1 } : {}), diff --git a/pages/api/level-by-slug/[username]/[slugName].ts b/pages/api/level-by-slug/[username]/[slugName].ts index 88c933e39..880488753 100644 --- a/pages/api/level-by-slug/[username]/[slugName].ts +++ b/pages/api/level-by-slug/[username]/[slugName].ts @@ -90,6 +90,7 @@ export async function getLevelByUrlPath(username: string, slugName: string, reqU authorNote: 1, calc_difficulty_estimate: 1, calc_playattempts_just_beaten_count: 1, + isRanked: 1, ts: 1, } }, diff --git a/pages/profile/[name]/[[...tab]]/index.tsx b/pages/profile/[name]/[[...tab]]/index.tsx index 92165a470..dd75ffcd5 100644 --- a/pages/profile/[name]/[[...tab]]/index.tsx +++ b/pages/profile/[name]/[[...tab]]/index.tsx @@ -429,6 +429,8 @@ export default function ProfilePage({ levelsSolvedByDifficulty ? : '...' } + {/* TODO: h2?? */} +

Ranked Solves: {user.calcRankedSolves}

Levels Solved: {user.score}

Levels Created: {user.calc_levels_created_count}

{!user.hideStatus && <> diff --git a/pages/search/index.tsx b/pages/search/index.tsx index ac32bc7df..5658579d2 100644 --- a/pages/search/index.tsx +++ b/pages/search/index.tsx @@ -326,12 +326,54 @@ export default function Search({ enrichedLevels, reqUser, searchAuthor, searchQu }, [loading, queryDebounce]); const columns = [ + { + id: 'name', + name: 'Name', + grow: 2, + selector: (row: EnrichedLevel) => ( + +
+ +
+ {row.isRanked && + + + + } +
+ { + const q = getParsedUrlQuery(query); + const queryString = Object.keys(q).map(key => key + '=' + query[key]).join('&'); + const ts = new Date(); + + // TODO: temp collection is a hack (doesn't represent a real collection so there are other UX problems) + // should make a new collection class to be used on the level page (with an href property, isInMemory, etc.) + const collectionTemp = { + createdAt: ts, + isPrivate: true, + levels: data, _id: new Types.ObjectId(), + name: 'Search', + slug: `../search${queryString ? `?${queryString}` : ''}`, + type: CollectionType.InMemory, + updatedAt: ts, + userId: { _id: new Types.ObjectId() } as Types.ObjectId & User, + } as EnrichedCollection; + + sessionStorage.setItem('tempCollection', JSON.stringify(collectionTemp)); + setTempCollection(collectionTemp); + }} id='search' level={row} /> +
+ ), + sortable: true, + style: { + minWidth: '150px', + }, + }, { id: 'userId', name: 'Author', selector: (row: EnrichedLevel) => (
-