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) => (
-