Updates public servers design and api

Reworks the design of the public servers module to be more like Discord's server discovery feature.

Updates some of the API and terminology to match that of DiscordServers.com
This commit is contained in:
Zack Rauen 2020-10-24 02:13:16 -04:00
parent eb963ce247
commit 60865aaaf2
9 changed files with 362 additions and 223 deletions

View File

@ -11,7 +11,8 @@ export default {
"**Floating editors** for both custom css and plugins/themes are now available. (See video above)",
"**Settings panels** are completely new and sleek. They are also highly extensible for potential future features :eyes:",
"**Translations** are now integrated starting with only a couple languages, but feel free to contribute your own!",
"**Emote menu** now uses React Patching and properly integrates into the new Emoji Picker. (Thanks Strencher#1044!)"
"**Emote menu** now uses React Patching and properly integrates into the new Emoji Picker. (Thanks Strencher#1044!)",
"**Public servers** got a new makeover thanks to some design help from Tropical and Gibbu!"
]
},
{

View File

@ -251,11 +251,13 @@ export default {
joined: "Joined",
loading: "Loading",
loadMore: "Load More",
notConnected: "Not connected to DiscordServers.com!",
notConnected: "Not Connected",
connectionRequired: "You must connect your account in order to join servers.",
search: "Search",
connect: "Connect",
reconnect: "Reconnect",
categories: "Categories",
keywords: "Keywords",
connection: "Connected as: {{username}}#{{discriminator}}",
results: "Showing {{start}}-{{end}} of {{total}} results in {{category}}",
query: "for {{query}}"
@ -264,6 +266,7 @@ export default {
confirmAction: "Are You Sure?",
okay: "Okay",
cancel: "Cancel",
nevermind: "Nevermind",
close: "Close",
name: "Name",
message: "Message",

View File

@ -21,11 +21,11 @@ export default class PublicServersConnection {
return SortedGuildStore.getFlattenedGuildIds().includes(id);
}
static search({term = "", category = "", from = 0} = {}) {
static search({term = "", keyword = "", from = 0} = {}) {
const request = require("request");
return new Promise(resolve => {
const queries = [];
if (category) queries.push(`category=${category.replace(/ /g, "%20")}`);
if (keyword) queries.push(`keyword=${keyword.replace(/ /g, "%20").toLowerCase()}`);
if (term) queries.push(`term=${term.replace(/ /g, "%20")}`);
if (from) queries.push(`from=${from}`);
const query = `?${queries.join("&")}`;
@ -64,7 +64,7 @@ export default class PublicServersConnection {
static async checkConnection() {
try {
const response = await fetch(`https://auth.discordservers.com/info`,{
const response = await fetch(this.connectEndPoint, {
method: "GET",
credentials: "include",
mode: "cors",
@ -82,6 +82,19 @@ export default class PublicServersConnection {
}
}
static async getDashboard() {
try {
const response = await fetch(`${this.endPoint}/dashboard`, {
method: "GET"
});
const data = await response.json();
return data;
}
catch (error) {
return false;
}
}
static connect() {
return new Promise(resolve => {
const joinWindow = new BrowserWindow(this.windowOptions);

View File

@ -14,107 +14,6 @@
height: 20px;
}
.bd-server-card .bd-server-tags {
flex: 1 1 auto;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: 0;
line-height: 24px;
font-size: 12px;
color: #b9bbbe;
font-weight: 700;
margin-right: 10px;
}
/* .ui-card.ui-card-primary.bd-server-card:first-child {
margin-bottom: 13px;
}
.ui-card.ui-card-primary.bd-server-card:first-child:after {
border: 3px solid #7289da;
content: "";
display: block;
position: absolute;
left: 0;
right: 0;
margin-top: 4px;
} */
.bd-server-card.bd-server-card-pinned {
margin-bottom: 15px;
}
.bd-server-card.bd-server-card-pinned::after {
background: #3a71c1;
content: "";
height: 3px;
width: 100%;
display: block;
margin-top: 7px;
position: absolute;
top: 100%;
}
.bd-server-description-container {
color: #b9bbbe;
min-height: 65px;
max-height: 65px;
border-top: 1px solid #3f4146;
border-bottom: 1px solid #3f4146;
padding-top: 5px;
font-size: 13px;
}
.bd-server-header {
text-transform: uppercase;
letter-spacing: 0.5px;
justify-content: space-between;
font-weight: 600;
}
.bd-server-card {
display: flex;
position: relative;
border-width: 1px;
border-style: solid;
border-radius: 5px;
background: rgba(32, 34, 37, 0.6);
border-color: #202225;
margin-bottom: 8px;
}
.bd-server-header,
.bd-server-footer {
display: flex;
color: #b9bbbe;
}
.bd-server-card .bd-button {
margin-top: 4px;
}
.bd-server-content {
padding: 5px 10px;
flex: 1;
}
.bd-server-image {
min-width: 115px;
min-height: 115px;
max-width: 115px;
max-height: 115px;
}
.bd-server-name {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding-right: 15px;
max-width: 330px;
flex: 1 1 50%;
}
.bd-layer {
-ms-flex-direction: column;
-webkit-box-direction: normal;
@ -130,66 +29,6 @@
top: 0;
}
/* #pubslayer .ui-tab-bar-item {
color: #b9bbbe;
padding-top: 6px;
padding-bottom: 6px;
margin-bottom: 2px;
padding: 6px 10px;
position: relative;
font-size: 16px;
line-height: 20px;
border-radius: 3px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
flex-shrink: 0;
font-weight: 500;
cursor: pointer;
}
#pubslayer .ui-tab-bar-item:hover {
color: #f6f6f7;
background-color: hsla(216,4%,74%,.1);
}
#pubslayer .ui-tab-bar-item.selected {
color: #fff;
background-color: #7289da;
}
#pubslayer .ui-tab-bar-header {
color: #72767d;
padding: 6px 10px;
font-size: 12px;
line-height: 16px;
text-transform: uppercase;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
flex-shrink: 0;
font-weight: 500;
}
#pubslayer #bd-settings-sidebar .ui-tab-bar-separator {
background-color: hsla(218,5%,47%,.3);
margin-left: 10px;
margin-right: 10px;
height: 1px;
margin-bottom: 8px;
margin-top: 8px;
}
#pubslayer h2.ui-form-title {
color: #f6f6f7;
text-transform: uppercase;
font-weight: 600;
}
#pubslayer h5.ui-form-title {
color: #f6f6f7;
} */
#pubslayer button {
background: #7289da;
color: #fff;
@ -233,4 +72,181 @@
margin: 5px 10px 10px 0;
width: 100%;
min-height: 20px;
}
/* Rewrite */
.bd-server-search {
margin-bottom: 5px;
}
.bd-empty-results {
display: flex;
flex-direction: column;
align-items: center;
color: var(--text-normal);
margin-top: 100px;
}
.bd-empty-results svg {
margin-bottom: 20px;
}
.bd-card-list {
display: grid;
grid-gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
}
.bd-server-card {
display: flex;
flex-direction: column;
height: 320px;
width: 100%;
overflow: hidden;
border-radius: 8px;
position: relative;
transition: box-shadow 0.2s ease-out, transform 0.2s ease-out, background 0.2s ease-out, opacity 0.2s ease-in;
cursor: pointer;
background-color: var(--background-secondary-alt);
}
.bd-server-card:hover {
background-color: var(--background-tertiary);
transform: translateY(-1px);
box-shadow: var(--elevation-high);
}
.bd-server-header {
height: 143px;
position: relative;
display: block;
overflow: visible;
margin-bottom: 32px;
}
.bd-server-splash-container {
width: 100%;
height: 100%;
display: block;
position: absolute;
top: 0;
left: 0;
transition: opacity 0.2s, transform 0.2s ease-out;
transform: scale(1);
overflow: hidden;
}
.bd-server-card:hover .bd-server-splash-container {
-webkit-transform: scale(1.01) translateZ(0);
transform: scale(1.01) translateZ(0);
}
.bd-server-splash {
object-fit: cover;
width: 100%;
height: 100%;
filter: blur(20px);
}
.bd-server-icon {
position: absolute;
bottom: -21px;
left: 12px;
width: 40px;
background: var(--background-secondary-alt);
border: 4px solid var(--background-secondary-alt);
border-radius: 25%;
transition: background 0.2s ease-out, transform 0.2s ease-out, border-color 0.2s ease-out;
}
.bd-server-card:hover .bd-server-icon {
border-color: var(--background-tertiary);
background: var(--background-tertiary);
}
.bd-server-info {
display: flex;
flex: 1 1 auto;
position: relative;
flex-direction: column;
align-content: stretch;
padding: 0 16px 16px;
overflow: hidden;
}
.bd-server-title {
display: flex;
align-items: center;
width: 100%;
font-weight: 600;
}
.bd-server-name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--header-primary);
font-size: 16px;
line-height: 20px;
}
.bd-server-description {
flex: 1 1 auto;
overflow: hidden;
margin: 4px 0 16px;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
color: var(--header-secondary);
font-size: 14px;
line-height: 18px;
}
.bd-server-footer {
display: flex;
align-items: center;
}
.bd-server-count {
display: flex;
align-items: center;
font-size: 0.75rem;
line-height: 1rem;
margin-right: 16px;
}
.bd-server-count-dot {
background-color: #43b581;
border-radius: 50%;
width: 8px;
height: 8px;
margin-right: 4px;
flex-shrink: 0;
}
.bd-server-count + .bd-server-count .bd-server-count-dot {
background-color: #b9bbbe;
}
.bd-server-count-text {
color: var(--header-secondary);
font-size: 12px;
line-height: 16px;
}
.bd-server-tag {
margin-left: 5px;
font-size: 10px;
text-transform: uppercase;
vertical-align: top;
display: inline-flex;
align-items: center;
flex-shrink: 0;
text-indent: 0;
height: 15px;
padding: 0 4px;
margin-top: 1px;
border-radius: 3px;
background: #3e82e5;
color: #fff;
}

View File

@ -0,0 +1,23 @@
import {React} from "modules";
export default class MagnifyingGlass extends React.Component {
render() {
const size = this.props.size || "160px";
return <svg xmlns="http://www.w3.org/2000/svg" style={{width: size, height: size}} viewBox="0 0 160 160">
<g fill="none" fillRule="evenodd">
<g transform="translate(9 9)">
<path fill="#4A4D51" d="M42.1262,100.7598 C25.1382,83.7718 25.1382,56.2288 42.1262,39.2408 C59.1142,22.2538 86.6572,22.2538 103.6452,39.2408 C120.6322,56.2288 120.6322,83.7718 103.6452,100.7598 C86.6572,117.7478 59.1142,117.7478 42.1262,100.7598"/>
<path stroke="#1E2126" strokeWidth="2" d="M121.8938,119.4976 C94.5578,146.8346 50.2358,146.8346 22.8988,119.4976 C-4.4382,92.1616 -4.4382,47.8396 22.8988,20.5026 C50.2358,-6.8334 94.5578,-6.8344 121.8938,20.5026 C149.2308,47.8396 149.2308,92.1616 121.8938,119.4976 Z" strokeLinecap="round" strokeLinejoin="round" strokeDasharray="4 5"/>
<path fill="#C9D2F0" d="M1.8313,140.566 L1.8313,140.566 C-0.6097,138.125 -0.6097,134.166 1.8313,131.725 L38.6023,94.954 L47.4433,103.795 L10.6723,140.566 C8.2303,143.007 4.2723,143.007 1.8313,140.566"/>
<path stroke="#1E2126" strokeWidth="2" d="M1.8313,140.566 L1.8313,140.566 C-0.6097,138.125 -0.6097,134.166 1.8313,131.725 L38.6023,94.954 L47.4433,103.795 L10.6723,140.566 C8.2303,143.007 4.2723,143.007 1.8313,140.566 Z" strokeLinecap="round" strokeLinejoin="round"/>
<path fill="#9F7373" d="M12.1457,139.0923 L3.3047,130.2513 C1.6767,128.6233 1.6767,125.9853 3.3047,124.3573 L20.7417,106.9203 C22.3687,105.2923 25.0077,105.2923 26.6357,106.9203 L35.4767,115.7613 C37.1037,117.3893 37.1037,120.0283 35.4767,121.6553 L18.0397,139.0923 C16.4127,140.7193 13.7727,140.7193 12.1457,139.0923"/>
<path stroke="#1E2126" strokeWidth="2" d="M12.1457,139.0923 L3.3047,130.2513 C1.6767,128.6233 1.6767,125.9853 3.3047,124.3573 L20.7417,106.9203 C22.3687,105.2923 25.0077,105.2923 26.6357,106.9203 L35.4767,115.7613 C37.1037,117.3893 37.1037,120.0283 35.4767,121.6553 L18.0397,139.0923 C16.4127,140.7193 13.7727,140.7193 12.1457,139.0923 Z" strokeLinecap="round" strokeLinejoin="round"/>
<path fill="#F3F9FF" d="M44.112,98.2847 C28.491,82.6637 28.491,57.3377 44.112,41.7167 C59.733,26.0957 85.06,26.0957 100.681,41.7157 C116.302,57.3367 116.302,82.6637 100.681,98.2847 C85.06,113.9057 59.733,113.9057 44.112,98.2847 M108.007,34.3897 C88.34,14.7227 56.453,14.7227 36.786,34.3897 C17.119,54.0567 17.119,85.9437 36.786,105.6107 C56.453,125.2777 88.34,125.2777 108.007,105.6107 C127.674,85.9437 127.674,54.0567 108.007,34.3897"/>
<path stroke="#1E2126" strokeWidth="2" d="M116.386 94.545C115.853 95.498 115.287 96.438 114.688 97.362M108.0071 105.6109C88.3401 125.2779 56.4531 125.2779 36.7861 105.6109 17.1191 85.9439 17.1191 54.0569 36.7861 34.3899 56.4531 14.7229 88.3401 14.7229 108.0071 34.3899 122.7701 49.1529 126.4511 70.7999 119.0511 88.9969" strokeLinecap="round" strokeLinejoin="round"/>
<path stroke="#1E2126" strokeWidth="2" d="M44.112,98.2847 C28.491,82.6637 28.491,57.3377 44.112,41.7167 C59.733,26.0957 85.06,26.0957 100.681,41.7157 C116.302,57.3367 116.302,82.6637 100.681,98.2847 C85.06,113.9057 59.733,113.9057 44.112,98.2847 Z" strokeLinecap="round" strokeLinejoin="round"/>
</g>
<rect width="160" height="160" y="-1"/>
</g>
</svg>;
}
}

View File

@ -1,5 +1,20 @@
import {React, Strings} from "modules";
const badge = <div className="flowerStarContainer-3zDVtj verified-1eC5dy background-2uufRq guildBadge-RlDbED"
style={{width: "16px", height: "16px"}}>
<svg aria-label="Verified &amp; Partnered" className="flowerStar-1GeTsn"
aria-hidden="false" width="16" height="16" viewBox="0 0 16 15.2">
<path fill="currentColor" fillRule="evenodd"
d="m16 7.6c0 .79-1.28 1.38-1.52 2.09s.44 2 0 2.59-1.84.35-2.46.8-.79 1.84-1.54 2.09-1.67-.8-2.47-.8-1.75 1-2.47.8-.92-1.64-1.54-2.09-2-.18-2.46-.8.23-1.84 0-2.59-1.54-1.3-1.54-2.09 1.28-1.38 1.52-2.09-.44-2 0-2.59 1.85-.35 2.48-.8.78-1.84 1.53-2.12 1.67.83 2.47.83 1.75-1 2.47-.8.91 1.64 1.53 2.09 2 .18 2.46.8-.23 1.84 0 2.59 1.54 1.3 1.54 2.09z">
</path>
</svg>
<div className="childContainer-1wxZNh">
<svg className="icon-1ihkOt" aria-hidden="false" width="16" height="16" viewBox="0 0 16 15.2">
<path d="M7.4,11.17,4,8.62,5,7.26l2,1.53L10.64,4l1.36,1Z" fill="currentColor"></path>
</svg>
</div>
</div>;
export default class ServerCard extends React.Component {
constructor(props) {
super(props);
@ -14,24 +29,33 @@ export default class ServerCard extends React.Component {
render() {
const {server} = this.props;
const addedDate = new Date(server.insertDate * 1000); // Convert from unix timestamp
const buttonText = typeof(this.state.joined) == "string" ? `${Strings.PublicServers.joining}...` : this.state.joined ? Strings.PublicServers.joined : Strings.PublicServers.join;
const buttonClass = `bd-button${this.state.joined == true ? " bd-button-success" : ""}`;
return <div className={`bd-server-card${server.pinned ? " bd-server-card-pinned" : ""}`}>
<img className="bd-server-image" src={server.iconUrl} onError={this.handleError} />,
<div className="bd-server-content">
return <div className="bd-server-card" role="button" tabIndex="0" onClick={this.join}>
<div className="bd-server-header">
<h5 className="bd-server-name">{server.name}</h5>
<h5 className="bd-server-member-count">{server.members} Members</h5>
</div>
<div className="bd-scroller-wrap bd-server-description-container">
<div className="bd-scroller bd-server-description">{server.description}</div>
<div className="bd-server-splash-container"><img src={server.iconUrl} onError={this.handleError} className="bd-server-splash" /></div>
<img src={server.iconUrl} onError={this.handleError} className="bd-server-icon" />
</div>
<div className="bd-server-footer">
<div className="bd-server-tags">{server.categories.join(", ")}</div>
<button type="button" className={buttonClass} onClick={this.join}>{buttonText}</button>
<div className="bd-server-info">
<div className="bd-server-title">
{server.pinned && badge}
<div className="bd-server-name">{server.name}</div>
{this.state.joined && <div className="bd-server-tag">{buttonText}</div>}
</div>
</div>
</div>;
<div className="bd-server-description">{server.description}</div>
<div className="bd-server-footer">
<div className="bd-server-count">
<div className="bd-server-count-dot"></div>
<div className="bd-server-count-text">{server.members.toLocaleString()} Members</div>
</div>
<div className="bd-server-count">
<div className="bd-server-count-dot"></div>
<div className="bd-server-count-text">Added {addedDate.toLocaleDateString()}</div>
</div>
</div>
</div>
</div>;
}
handleError() {
@ -40,7 +64,7 @@ export default class ServerCard extends React.Component {
}
async join() {
if (this.state.joined) return;
if (this.state.joined) return this.props.navigateTo(this.props.server.identifier);
this.setState({joined: "joining"});
const didJoin = await this.props.join(this.props.server.identifier, this.props.server.nativejoin);
this.setState({joined: didJoin});

View File

@ -1,21 +1,35 @@
import {React, WebpackModules, Strings} from "modules";
import Modals from "../modals";
import SettingsTitle from "../settings/title";
import ServerCard from "./card";
import EmptyResults from "./noresults";
import Connection from "../../structs/psconnection";
import Search from "../settings/components/search";
const SettingsView = WebpackModules.getByDisplayName("SettingsView");
const GuildActions = WebpackModules.getByProps("transitionToGuildSync");
const LayerManager = WebpackModules.getByProps("popLayer");
const betterDiscordServer = {
name: "BetterDiscord",
members: 55000,
categories: ["community", "programming", "support"],
description: "Official BetterDiscord server for plugins, themes, support, etc",
identifier: "86004744966914048",
iconUrl: "https://cdn.discordapp.com/icons/86004744966914048/292e7f6bfff2b71dfd13e508a859aedd.webp",
nativejoin: true,
invite_code: "BJD2yvJ",
pinned: true,
insertDate: 1517806800
};
export default class PublicServers extends React.Component {
get categoryButtons() {
return ["All", "FPS Games", "MMO Games", "Strategy Games", "MOBA Games", "RPG Games", "Tabletop Games", "Sandbox Games", "Simulation Games", "Music", "Community", "Language", "Programming", "Other"];
}
constructor(props) {
super(props);
this.state = {
category: "All",
tab: "Featured",
query: "",
loading: true,
user: null,
@ -28,24 +42,44 @@ export default class PublicServers extends React.Component {
}
};
this.changeCategory = this.changeCategory.bind(this);
this.featured = [];
this.popular = [];
this.keywords = [];
this.changeTab = this.changeTab.bind(this);
this.searchKeyDown = this.searchKeyDown.bind(this);
this.connect = this.connect.bind(this);
this.loadNextPage = this.loadNextPage.bind(this);
this.join = this.join.bind(this);
this.navigateTo = this.navigateTo.bind(this);
}
componentDidMount() {
this.getDashboard();
this.checkConnection();
}
async checkConnection() {
const userData = await Connection.checkConnection();
if (!userData) {
return this.setState({loading: true, user: null});
}
if (!userData) return this.setState({user: null});
this.setState({user: userData});
this.search();
}
async getDashboard() {
const dashboardData = await Connection.getDashboard();
const featuredFirst = dashboardData.results[0].key === "featured";
const featuredServers = dashboardData.results[featuredFirst ? 0 : 1].response.hits;
const popularServers = dashboardData.results[featuredFirst ? 1 : 0].response.hits;
const mainKeywords = dashboardData.mainKeywords.map(k => k.charAt(0).toUpperCase() + k.slice(1)).sort();
featuredServers.unshift(betterDiscordServer);
this.featured = featuredServers;
this.popular = popularServers;
this.keywords = mainKeywords;
this.setState({loading: false});
this.changeTab(this.state.tab);
}
async connect() {
@ -55,12 +89,14 @@ export default class PublicServers extends React.Component {
searchKeyDown(e) {
if (this.state.loading || e.which !== 13) return;
this.search(e.target.value);
const term = e.target.value;
if (this.state.tab == "Featured" || this.state.tab == "Popular") this.setState({tab: "All"}, () => this.search(term));
else this.search(term);
}
async search(term = "", from = 0) {
this.setState({query: term, loading: true});
const results = await Connection.search({term, category: this.state.category == "All" ? "" : this.state.category, from});
const results = await Connection.search({term, keyword: this.state.tab == "All" || this.state.tab == "Featured" || this.state.tab == "Popular" ? "" : this.state.tab, from});
if (!results) {
return this.setState({results: {
servers: [],
@ -70,12 +106,23 @@ export default class PublicServers extends React.Component {
next: null
}});
}
this.setState({loading: false, results});
}
async changeCategory(id) {
async changeTab(id) {
if (this.state.loading) return;
await new Promise(resolve => this.setState({category: id}, resolve));
await new Promise(resolve => this.setState({tab: id}, resolve));
if (this.state.tab === "Featured" || this.state.tab == "Popular") {
return this.setState({results: {
servers: this[this.state.tab.toLowerCase()],
size: this[this.state.tab.toLowerCase()].length,
from: 0,
total: this[this.state.tab.toLowerCase()].length,
next: null
}});
}
this.search();
}
@ -85,33 +132,44 @@ export default class PublicServers extends React.Component {
}
async join(id, native = false) {
if (!this.state.user && !native) {
return Modals.showConfirmationModal(Strings.PublicServers.notConnected, Strings.PublicServers.connectionRequired, {
cancelText: Strings.Modals.nevermind,
confirmText: Strings.Modals.okay,
onConfirm: () => {
this.connect().then(() => Connection.join(id, native));
}
});
}
return await Connection.join(id, native);
}
navigateTo(id) {
if (GuildActions) GuildActions.transitionToGuildSync(id);
if (LayerManager) LayerManager.popLayer();
}
get searchBox() {
return <Search onKeyDown={this.searchKeyDown} placeholder={`${Strings.PublicServers.search}...`} />;
return <Search onKeyDown={this.searchKeyDown} className="bd-server-search" placeholder={`${Strings.PublicServers.search}...`} />;
}
get title() {
if (!this.state.user) return Strings.PublicServers.notConnected;
if (this.state.loading) return `${Strings.PublicServers.loading}...`;
const start = this.state.results.from + 1;
const total = this.state.results.total;
const end = this.state.results.next ? this.state.results.next : total;
let title = Strings.PublicServers.results.format({start, end, total, category: this.state.category});
let title = Strings.PublicServers.results.format({start, end, total, category: this.state.tab});
if (this.state.query) title += " " + Strings.PublicServers.query.format({query: this.state.query});
return title;
}
get content() {
const connectButton = this.state.user ? null : {title: Strings.PublicServers.connect, onClick: this.connect};
const pinned = this.state.category == "All" || !this.state.user ? this.bdServer : null;
const servers = this.state.results.servers.map((server) => {
return React.createElement(ServerCard, {key: server.identifier, server: server, joined: Connection.hasJoined(server.identifier), join: this.join, defaultAvatar: Connection.getDefaultAvatar});
return React.createElement(ServerCard, {key: server.identifier, server: server, joined: Connection.hasJoined(server.identifier), join: this.join, navigateTo: this.navigateTo, defaultAvatar: Connection.getDefaultAvatar});
});
return [React.createElement(SettingsTitle, {text: this.title, button: connectButton}),
pinned,
servers,
this.state.results.total ? React.createElement("div", {className: "bd-card-list"}, servers) : React.createElement(EmptyResults),
this.state.results.next ? this.nextButton : null,
this.state.results.servers.length > 0 && React.createElement(SettingsTitle, {text: this.title})];
}
@ -129,24 +187,8 @@ export default class PublicServers extends React.Component {
);
}
get bdServer() {
const server = {
name: "BetterDiscord",
online: "7500+",
members: "20000+",
categories: ["community", "programming", "support"],
description: "Official BetterDiscord server for plugins, themes, support, etc",
identifier: "86004744966914048",
iconUrl: "https://cdn.discordapp.com/icons/86004744966914048/292e7f6bfff2b71dfd13e508a859aedd.webp",
nativejoin: true,
invite_code: "0Tmfo5ZbORCRqbAd",
pinned: true
};
return React.createElement(ServerCard, {server: server, pinned: true, joined: Connection.hasJoined(server.identifier), defaultAvatar: Connection.getDefaultAvatar});
}
render() {
const categories = this.categoryButtons.map(name => ({
const keywords = this.keywords.map(name => ({
section: name,
label: name,
element: () => this.content
@ -154,13 +196,19 @@ export default class PublicServers extends React.Component {
);
return React.createElement(SettingsView, {
onClose: this.props.close,
onSetSection: this.changeCategory,
section: this.state.category,
onSetSection: this.changeTab,
section: this.state.tab,
sections: [
{section: "HEADER", label: Strings.PublicServers.search},
{section: "CUSTOM", element: () => this.searchBox},
{section: "DIVIDER"},
{section: "HEADER", label: Strings.PublicServers.categories},
...categories,
{section: "All", label: "All", element: () => this.content},
{section: "Featured", label: "Featured", element: () => this.content},
{section: "Popular", label: "Popular", element: () => this.content},
{section: "DIVIDER"},
{section: "HEADER", label: Strings.PublicServers.keywords},
...keywords,
{section: "DIVIDER"},
{section: "HEADER", label: React.createElement("a", {href: "https://discordservers.com", target: "_blank"}, "DiscordServers.com")},
{section: "DIVIDER"},

View File

@ -0,0 +1,11 @@
import {React, DiscordModules} from "modules";
import MagnifyingGlass from "../icons/magnifyingglass";
export default class NoResults extends React.Component {
render() {
return <div className={"bd-empty-results" + (this.props.className ? ` ${this.props.className}` : "")}>
<MagnifyingGlass />
{DiscordModules.Strings.SEARCH_NO_RESULTS || ""}
</div>;
}
}

View File

@ -3,7 +3,7 @@ import SearchIcon from "../../icons/search";
export default class Search extends React.Component {
render() {
return <div className="bd-search-wrapper">
return <div className={"bd-search-wrapper" + (this.props.className ? ` ${this.props.className}` : "")}>
<input onChange={this.props.onChange} onKeyDown={this.props.onKeyDown} type="text" className="bd-search" placeholder={this.props.placeholder} maxLength="50" />
<SearchIcon />
</div>;