Remove public servers (#1577)
This commit is contained in:
parent
2aa71d87d9
commit
cec522189f
|
@ -2,7 +2,6 @@
|
|||
|
||||
export {default as CustomCSS} from "./customcss";
|
||||
|
||||
export {default as PublicServers} from "./general/publicservers";
|
||||
export {default as VoiceDisconnect} from "./general/voicedisconnect";
|
||||
export {default as MediaKeys} from "./general/mediakeys";
|
||||
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
import Builtin from "../../structs/builtin";
|
||||
import {DiscordModules, WebpackModules, Strings, DOMManager, React, ReactDOM} from "modules";
|
||||
import PublicServersMenu from "../../ui/publicservers/menu";
|
||||
import Globe from "../../ui/icons/globe";
|
||||
|
||||
const LayerManager = {
|
||||
pushLayer(component) {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_PUSH",
|
||||
component
|
||||
});
|
||||
},
|
||||
popLayer() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP"
|
||||
});
|
||||
},
|
||||
popAllLayers() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP_ALL"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {hasError: false};
|
||||
}
|
||||
|
||||
componentDidCatch() {
|
||||
this.setState({hasError: true});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) return null;
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default new class PublicServers extends Builtin {
|
||||
get name() {return "PublicServers";}
|
||||
get category() {return "general";}
|
||||
get id() {return "publicServers";}
|
||||
|
||||
enabled() {
|
||||
const PrivateChannelList = WebpackModules.getModule(m => m?.toString().includes("privateChannelIds"), {defaultExport: false});
|
||||
if (!PrivateChannelList || !PrivateChannelList.Z) return this.warn("Could not find PrivateChannelList", PrivateChannelList);
|
||||
const PrivateChannelButton = WebpackModules.getModule(m => m?.prototype?.render?.toString().includes("linkButton"), {searchExports: true});
|
||||
if (!PrivateChannelButton) return this.warn("Could not find PrivateChannelButton", PrivateChannelButton);
|
||||
|
||||
this.after(PrivateChannelList, "Z", (_, __, returnValue) => {
|
||||
const destination = returnValue?.props?.children?.props?.children;
|
||||
if (!destination || !Array.isArray(destination)) return;
|
||||
if (destination.find(b => b?.props?.children?.props?.id === "public-servers-button")) return; // If it exists, don't try to add again
|
||||
|
||||
destination.push(
|
||||
React.createElement(ErrorBoundary, null,
|
||||
React.createElement(PrivateChannelButton,
|
||||
{
|
||||
id: "public-servers-button",
|
||||
onClick: () => this.openPublicServers(),
|
||||
text: Strings.PublicServers.button,
|
||||
icon: () => React.createElement(Globe, {color: "currentColor"})
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* On being first enabled, we have no way of forceUpdating the list,
|
||||
* so clone and modify an existing button and add it to the end
|
||||
* of the button list.
|
||||
*/
|
||||
const header = document.querySelector(`[class*="privateChannelsHeaderContainer-"]`);
|
||||
if (!header) return; // No known element
|
||||
const oldButton = header.previousElementSibling;
|
||||
if (!oldButton.className.includes("channel-")) return; // Not what we expected to be there
|
||||
|
||||
// Clone existing button and set click handler
|
||||
const newButton = oldButton.cloneNode(true);
|
||||
newButton.addEventListener("click", (event) => {
|
||||
event.stopImmediatePropagation();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.openPublicServers();
|
||||
});
|
||||
|
||||
// Remove existing route and id
|
||||
const aSlot = newButton.querySelector("a");
|
||||
aSlot.href = "";
|
||||
aSlot.dataset.listItemId = "public-servers";
|
||||
|
||||
// Remove any badges
|
||||
const premiumBadge = newButton.querySelector(`[class*="premiumTrial"]`);
|
||||
premiumBadge?.remove?.();
|
||||
const numberBadge = newButton.querySelector(`[class*="numberBadge-"]`);
|
||||
numberBadge?.remove?.();
|
||||
|
||||
// Render our icon in the avatar slot
|
||||
const avatarSlot = newButton.querySelector(`[class*="avatar-"]`);
|
||||
avatarSlot.replaceChildren();
|
||||
ReactDOM.render(React.createElement(Globe, {color: "currentColor"}), avatarSlot);
|
||||
DOMManager.onRemoved(avatarSlot, () => ReactDOM.unmountComponentAtNode(avatarSlot));
|
||||
|
||||
// Replace the existing name
|
||||
const nameSlot = newButton.querySelector(`[class*="name-"]`);
|
||||
nameSlot.textContent = Strings.PublicServers.button;
|
||||
|
||||
// Insert before the header, end of the list
|
||||
header.parentNode.insertBefore(newButton, header);
|
||||
|
||||
this.button = newButton;
|
||||
}
|
||||
|
||||
disabled() {
|
||||
this.unpatchAll();
|
||||
this.button?.remove?.();
|
||||
document.querySelector("#public-servers-button")?.parentElement?.parentElement?.remove?.();
|
||||
}
|
||||
|
||||
async _appendButton() {
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
const existing = document.querySelector("#bd-pub-li");
|
||||
if (existing) return;
|
||||
|
||||
const guilds = document.querySelector(`.${DiscordModules.GuildClasses.guilds} .${DiscordModules.GuildClasses.listItem}`);
|
||||
if (!guilds) return;
|
||||
|
||||
guilds.parentNode.insertBefore(this.button, guilds.nextSibling);
|
||||
}
|
||||
|
||||
openPublicServers() {
|
||||
LayerManager.pushLayer(() => DiscordModules.React.createElement(PublicServersMenu, {close: LayerManager.popLayer}));
|
||||
}
|
||||
};
|
|
@ -4,7 +4,6 @@ export default [
|
|||
id: "general",
|
||||
collapsible: true,
|
||||
settings: [
|
||||
{type: "switch", id: "publicServers", value: true},
|
||||
{type: "switch", id: "voiceDisconnect", value: false},
|
||||
{type: "switch", id: "showToasts", value: true},
|
||||
{type: "switch", id: "mediaKeys", value: false}
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
import Logger from "common/logger";
|
||||
import {WebpackModules, IPC} from "modules";
|
||||
|
||||
const SortedGuildStore = WebpackModules.getByProps("getSortedGuilds");
|
||||
const AvatarDefaults = WebpackModules.getByProps("DEFAULT_AVATARS");
|
||||
const InviteActions = WebpackModules.getByProps("acceptInvite");
|
||||
|
||||
// const BrowserWindow = require("electron").remote.BrowserWindow;
|
||||
|
||||
const betterDiscordServer = {
|
||||
name: "BetterDiscord",
|
||||
members: 110000,
|
||||
categories: ["community", "programming", "support"],
|
||||
description: "Official BetterDiscord server for plugins, themes, support, etc",
|
||||
identifier: "86004744966914048",
|
||||
iconUrl: "https://cdn.discordapp.com/icons/86004744966914048/babd1af3fa6011a50e418a80f4970ceb.webp",
|
||||
nativejoin: true,
|
||||
invite_code: "BJD2yvJ",
|
||||
pinned: true,
|
||||
insertDate: 1517806800
|
||||
};
|
||||
|
||||
const ITEMS_PER_PAGE = 50;
|
||||
|
||||
export default new class PublicServersConnection {
|
||||
|
||||
constructor() {
|
||||
this.cache = new Map();
|
||||
this.cache.set("featured", [betterDiscordServer]);
|
||||
this.cache.set("popular", []);
|
||||
this.cache.set("keywords", []);
|
||||
this.cache.set("accessToken", "");
|
||||
}
|
||||
|
||||
get endPoint() {return "https://search.discordservers.com";}
|
||||
get joinEndPoint() {return "https://j.discordservers.com";}
|
||||
get connectEndPoint() {return "https://auth.discordservers.com/info";}
|
||||
get authorizeEndPoint() {return `https://auth.discordservers.com/connect?scopes=guilds.join&previousUrl=${this.connectEndPoint}`;}
|
||||
|
||||
getDefaultAvatar() {
|
||||
return AvatarDefaults.DEFAULT_AVATARS[Math.floor(Math.random() * 5)];
|
||||
}
|
||||
|
||||
hasJoined(id) {
|
||||
return SortedGuildStore.getFlattenedGuildIds().includes(id);
|
||||
}
|
||||
|
||||
async search({term = "", keyword = "", page = 1} = {}) {
|
||||
if (this.cache.has(term + keyword + page)) return this.cache.get(term + keyword + page);
|
||||
|
||||
const from = (page - 1) * ITEMS_PER_PAGE;
|
||||
const queries = [];
|
||||
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("&")}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.endPoint}${query}`, {method: "GET"});
|
||||
const data = await response.json();
|
||||
const results = {
|
||||
servers: data.results,
|
||||
size: data.size,
|
||||
total: data.total,
|
||||
page: Math.ceil(from / ITEMS_PER_PAGE) + 1,
|
||||
numPages: Math.ceil(data.total / ITEMS_PER_PAGE)
|
||||
};
|
||||
this.cache.set(term + keyword + page, results);
|
||||
return results;
|
||||
}
|
||||
catch (error) {
|
||||
Logger.stacktrace("PublicServers", "Could not reach search endpoint.", error);
|
||||
}
|
||||
}
|
||||
|
||||
async getDashboard() {
|
||||
const cachedKeywords = this.cache.get("keywords");
|
||||
if (cachedKeywords && cachedKeywords.length) return {featured: this.cache.get("featured"), popular: this.cache.get("popular"), keywords: cachedKeywords};
|
||||
try {
|
||||
const response = await fetch(`${this.endPoint}/dashboard`, {method: "GET"});
|
||||
const data = await response.json();
|
||||
|
||||
const featuredFirst = data.results[0].key === "featured";
|
||||
const featuredServers = data.results[featuredFirst ? 0 : 1].response.hits;
|
||||
const popularServers = data.results[featuredFirst ? 1 : 0].response.hits;
|
||||
const mainKeywords = data.mainKeywords.map(k => k.charAt(0).toUpperCase() + k.slice(1)).sort();
|
||||
|
||||
featuredServers.unshift(betterDiscordServer);
|
||||
|
||||
this.cache.set("featured", featuredServers);
|
||||
this.cache.set("popular", popularServers);
|
||||
this.cache.set("keywords", mainKeywords);
|
||||
return {featured: this.cache.get("featured"), popular: this.cache.get("popular"), keywords: this.cache.get("keywords")};
|
||||
}
|
||||
catch (error) {
|
||||
Logger.stacktrace("PublicServers", "Could not download dashboard.", error);
|
||||
return {featured: this.cache.get("featured"), popular: this.cache.get("popular"), keywords: this.cache.get("keywords")};
|
||||
}
|
||||
}
|
||||
|
||||
async join(id, native = false) {
|
||||
if (native) return InviteActions.acceptInvite({inviteKey: id});
|
||||
try {
|
||||
await fetch(`${this.joinEndPoint}/${id}`,{
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
Logger.warn("PublicServers", "Could not join server.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async checkConnection() {
|
||||
try {
|
||||
const response = await fetch(this.connectEndPoint, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
this._accessToken = data.access_token;
|
||||
return data;
|
||||
}
|
||||
catch (error) {
|
||||
Logger.warn("PublicServers", "Could not verify connection.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async connect() {
|
||||
await IPC.openWindow(this.authorizeEndPoint, {
|
||||
windowOptions: this.windowOptions,
|
||||
closeOnUrl: this.connectEndPoint
|
||||
});
|
||||
}
|
||||
|
||||
get windowOptions() {
|
||||
return {
|
||||
width: 520,
|
||||
height: 580,
|
||||
backgroundColor: "#282b30",
|
||||
show: true,
|
||||
resizable: true,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
alwaysOnTop: true,
|
||||
frame: false,
|
||||
center: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: false
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
|
@ -1,287 +0,0 @@
|
|||
@keyframes bd-placeholder-card-pulse {
|
||||
0% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
#bd-pub-li {
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#bd-pub-button {
|
||||
border-radius: 4px;
|
||||
background-color: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
transition: background-color 0.15s ease-out, color 0.15s ease-out;
|
||||
}
|
||||
|
||||
#bd-pub-button:hover {
|
||||
color: #FFFFFF;
|
||||
background-color: #3E82E5;
|
||||
}
|
||||
|
||||
#bd-connection {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.bd-footnote {
|
||||
color: #B9BBBE;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.bd-button-next,
|
||||
.bd-button-reconnect {
|
||||
margin: 5px 10px 10px 0;
|
||||
width: 100%;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
/* Rewrite */
|
||||
.bd-server-search {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.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(--activity-card-background);
|
||||
}
|
||||
|
||||
.theme-light .bd-server-card {
|
||||
box-shadow: 0 0 0 1px rgba(185, 187, 190, 0.3), var(--elevation-medium);
|
||||
}
|
||||
|
||||
.theme-dark .bd-server-card {
|
||||
background-color: var(--background-secondary-alt);
|
||||
}
|
||||
|
||||
.bd-server-card:hover,
|
||||
.bd-server-card:focus,
|
||||
.theme-light .bd-server-card:hover,
|
||||
.theme-light .bd-server-card:focus {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--elevation-high);
|
||||
}
|
||||
|
||||
.theme-dark .bd-server-card:hover,
|
||||
.theme-dark .bd-server-card:focus {
|
||||
background-color: var(--background-tertiary);
|
||||
}
|
||||
|
||||
.bd-placeholder-card {
|
||||
background-color: var(--background-secondary-alt);
|
||||
animation: bd-placeholder-card-pulse 1.8s ease-in-out infinite;
|
||||
height: 320px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.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: normal;
|
||||
}
|
||||
|
||||
.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: #FFFFFF;
|
||||
}
|
||||
|
||||
.bd-pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 15px;
|
||||
color: var(--header-primary);
|
||||
}
|
||||
|
||||
.bd-pagination span {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bd-pagination button {
|
||||
background: none;
|
||||
opacity: 0.7;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid var(--background-tertiary);
|
||||
border-radius: 3px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bd-pagination button:hover,
|
||||
.bd-pagination button:active {
|
||||
opacity: 1;
|
||||
background: var(--background-accent);
|
||||
}
|
||||
|
||||
.bd-pagination button:active {
|
||||
opacity: 1;
|
||||
background: var(--background-secondary);
|
||||
}
|
||||
|
||||
.bd-pagination button svg {
|
||||
fill: var(--header-primary);
|
||||
}
|
||||
|
||||
.bd-pagination button[disabled] {
|
||||
opacity: 0.2;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.bd-pagination + .bd-settings-title {
|
||||
margin-top: 20px;
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
import {React, Strings} from "modules";
|
||||
|
||||
const {useState, useCallback, useMemo} = React;
|
||||
|
||||
|
||||
const badge = <div className="flowerStarContainer-1QeD-L verified-1Jv_7P background-3Da2vZ rowIcon-2tDEcE" style={{width: "16px", height: "16px"}}>
|
||||
<svg aria-label="Verified & Partnered" className="flowerStar-2tNFCR" 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-U_a6Yh">
|
||||
<svg className="icon-3BYlXK" 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 function ServerCard({server, joined, join, navigateTo, defaultAvatar}) {
|
||||
const [isError, setError] = useState(false);
|
||||
const handleError = useCallback(() => {
|
||||
setError(true);
|
||||
}, []);
|
||||
|
||||
const [hasJoined, setJoined] = useState(joined);
|
||||
const doJoin = useCallback(async () => {
|
||||
if (hasJoined) return navigateTo(server.identifier);
|
||||
setJoined("joining");
|
||||
const didJoin = await join(server.identifier, server.nativeJoin);
|
||||
setJoined(didJoin);
|
||||
}, [hasJoined, join, navigateTo, server.identifier, server.nativeJoin]);
|
||||
|
||||
const defaultIcon = useMemo(() => defaultAvatar(), [defaultAvatar]);
|
||||
const currentIcon = !server.iconUrl || isError ? defaultIcon : server.iconUrl;
|
||||
|
||||
const addedDate = new Date(server.insertDate * 1000); // Convert from unix timestamp
|
||||
const buttonText = typeof(hasJoined) == "string" ? `${Strings.PublicServers.joining}...` : hasJoined ? Strings.PublicServers.joined : Strings.PublicServers.join;
|
||||
|
||||
return <div className="bd-server-card" role="button" tabIndex="0" onClick={doJoin}>
|
||||
<div className="bd-server-header">
|
||||
<div className="bd-server-splash-container"><img src={currentIcon} onError={handleError} className="bd-server-splash" /></div>
|
||||
<img src={currentIcon} onError={handleError} className="bd-server-icon" />
|
||||
</div>
|
||||
<div className="bd-server-info">
|
||||
<div className="bd-server-title">
|
||||
{server.pinned && badge}
|
||||
<div className="bd-server-name">{server.name}</div>
|
||||
{hasJoined && <div className="bd-server-tag">{buttonText}</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>;
|
||||
}
|
|
@ -1,251 +0,0 @@
|
|||
import {React, WebpackModules, Strings, DiscordModules} from "modules";
|
||||
import Modals from "../modals";
|
||||
import SettingsTitle from "../settings/title";
|
||||
import ServerCard from "./card";
|
||||
import EmptyResults from "../blankslates/noresults";
|
||||
import Connection from "../../structs/psconnection";
|
||||
import Search from "../settings/components/search";
|
||||
import Previous from "../icons/previous";
|
||||
import Next from "../icons/next";
|
||||
|
||||
const SettingsView = WebpackModules.getByPrototypes("renderSidebar");
|
||||
const GuildActions = WebpackModules.getByProps("transitionToGuildSync");
|
||||
const LayerManager = {
|
||||
pushLayer(component) {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_PUSH",
|
||||
component
|
||||
});
|
||||
},
|
||||
popLayer() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP"
|
||||
});
|
||||
},
|
||||
popAllLayers() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP_ALL"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const EMPTY_RESULTS = {
|
||||
servers: [],
|
||||
size: 0,
|
||||
total: 0,
|
||||
page: 1,
|
||||
numPages: 1
|
||||
};
|
||||
|
||||
export default class PublicServers extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tab: "Featured",
|
||||
query: "",
|
||||
loading: true,
|
||||
user: null,
|
||||
results: Object.assign({}, EMPTY_RESULTS)
|
||||
};
|
||||
|
||||
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.loadPreviousPage = this.loadPreviousPage.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({user: null});
|
||||
this.setState({user: userData});
|
||||
}
|
||||
|
||||
async getDashboard() {
|
||||
const dashboardData = await Connection.getDashboard();
|
||||
|
||||
this.featured = dashboardData.featured;
|
||||
this.popular = dashboardData.popular;
|
||||
this.keywords = dashboardData.keywords;
|
||||
|
||||
this.setState({loading: false});
|
||||
this.changeTab(this.state.tab);
|
||||
|
||||
if (!this.keywords || !this.keywords.length) Modals.showConfirmationModal(Strings.PublicServers.connectionError, Strings.PublicServers.connectionErrorMessage);
|
||||
}
|
||||
|
||||
async connect() {
|
||||
await Connection.connect();
|
||||
this.checkConnection();
|
||||
}
|
||||
|
||||
searchKeyDown(e) {
|
||||
if (this.state.loading || e.key !== "Enter") return;
|
||||
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 = "", page = 1) {
|
||||
this.setState({query: term, loading: true});
|
||||
const results = await Connection.search({term, keyword: this.state.tab == "All" || this.state.tab == "Featured" || this.state.tab == "Popular" ? "" : this.state.tab, page});
|
||||
if (!results) return this.setState({results: Object.assign({}, EMPTY_RESULTS)});
|
||||
|
||||
this.setState({loading: false, results});
|
||||
}
|
||||
|
||||
async changeTab(id) {
|
||||
if (this.state.loading) return;
|
||||
await new Promise(resolve => this.setState({tab: id}, resolve));
|
||||
if (this.state.tab === "Featured" || this.state.tab == "Popular") {
|
||||
const fakeResults = {
|
||||
servers: this[this.state.tab.toLowerCase()],
|
||||
size: this[this.state.tab.toLowerCase()].length,
|
||||
total: this[this.state.tab.toLowerCase()].length,
|
||||
page: 1,
|
||||
numPages: 1
|
||||
};
|
||||
return this.setState({results: fakeResults});
|
||||
}
|
||||
|
||||
this.search();
|
||||
}
|
||||
|
||||
get hasPrevious() {return this.state.results.page > 1;}
|
||||
get hasNext() {return this.state.results.page < this.state.results.numPages;}
|
||||
|
||||
loadPreviousPage() {
|
||||
if (this.state.loading || !this.hasPrevious) return;
|
||||
this.search(this.state.query, this.state.results.page - 1);
|
||||
}
|
||||
|
||||
loadNextPage() {
|
||||
if (this.state.loading || !this.hasNext) return;
|
||||
this.search(this.state.query, this.state.results.page + 1);
|
||||
}
|
||||
|
||||
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} className="bd-server-search" placeholder={`${Strings.PublicServers.search}...`} value={this.state.query} />;
|
||||
}
|
||||
|
||||
get title() {
|
||||
if (this.state.loading) return `${Strings.PublicServers.loading}...`;
|
||||
if (this.state.query) {
|
||||
const start = ((this.state.results.page - 1) * this.state.results.size) + 1;
|
||||
const total = this.state.results.total;
|
||||
const end = this.hasNext ? (start - 1) + this.state.results.size : total;
|
||||
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;
|
||||
}
|
||||
return this.state.tab;
|
||||
}
|
||||
|
||||
get content() {
|
||||
const connectButton = this.state.user ? null : {title: Strings.PublicServers.connect, onClick: this.connect};
|
||||
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, navigateTo: this.navigateTo, defaultAvatar: Connection.getDefaultAvatar});
|
||||
});
|
||||
|
||||
let content = React.createElement(EmptyResults);
|
||||
if (this.state.loading) content = this.loadingScreen;
|
||||
else if (this.state.results.total) content = React.createElement("div", {className: "bd-card-list"}, servers);
|
||||
|
||||
return [React.createElement(SettingsTitle, {text: this.title, button: connectButton}),
|
||||
this.state.results.numPages > 1 && this.pagination,
|
||||
content,
|
||||
this.state.results.numPages > 1 && this.pagination
|
||||
];
|
||||
}
|
||||
|
||||
get loadingScreen() {
|
||||
return <div className="bd-card-list">
|
||||
<div className="bd-placeholder-card"></div>
|
||||
<div className="bd-placeholder-card"></div>
|
||||
<div className="bd-placeholder-card"></div>
|
||||
<div className="bd-placeholder-card"></div>
|
||||
<div className="bd-placeholder-card"></div>
|
||||
<div className="bd-placeholder-card"></div>
|
||||
<div className="bd-placeholder-card"></div>
|
||||
<div className="bd-placeholder-card"></div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
get pagination() {
|
||||
return React.createElement("div", {className: "bd-pagination"},
|
||||
React.createElement("button", {type: "button", className: "bd-button bd-pagination-previous", disabled: !this.hasPrevious, onClick: this.loadPreviousPage}, <Previous />),
|
||||
React.createElement("span", {className: "bd-pagination-info"}, Strings.PublicServers.pagination.format({page: this.state.results.page, count: this.state.results.numPages})),
|
||||
React.createElement("button", {type: "button", className: "bd-button bd-pagination-next", disabled: !this.hasNext, onClick: this.loadNextPage}, <Next />)
|
||||
);
|
||||
}
|
||||
|
||||
get connection() {
|
||||
const {user} = this.state;
|
||||
if (!user) return React.createElement("div", {id: "bd-connection"});
|
||||
return React.createElement("div", {id: "bd-connection"},
|
||||
React.createElement("div", {className: "bd-footnote"}, Strings.PublicServers.connection.format(user)),
|
||||
React.createElement("button", {type: "button", className: "bd-button bd-button-reconnect", onClick: this.connect}, Strings.PublicServers.reconnect)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const keywords = this.keywords.map(name => ({
|
||||
section: name,
|
||||
label: name,
|
||||
element: () => this.content
|
||||
})
|
||||
);
|
||||
return React.createElement(SettingsView, {
|
||||
onClose: this.props.close,
|
||||
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},
|
||||
{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"},
|
||||
{section: "CUSTOM", element: () => this.connection}
|
||||
],
|
||||
theme: "dark"
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue