diff --git a/src/data/strings.js b/src/data/strings.js
index fdc14e9c..a2658907 100644
--- a/src/data/strings.js
+++ b/src/data/strings.js
@@ -261,6 +261,9 @@ export default {
loadMore: "Load More",
notConnected: "Not Connected",
connectionRequired: "You must connect your account in order to join servers.",
+ connectionError: "Connection Error",
+ connectionErrorMessage: "There was an error connecting to DiscordServers.com, it's possible their website/api is down. Please try again later.",
+ pagination: "Page {{page}} of {{count}}",
search: "Search",
connect: "Connect",
reconnect: "Reconnect",
diff --git a/src/structs/psconnection.js b/src/structs/psconnection.js
index 6a98e385..c8bf5a3d 100644
--- a/src/structs/psconnection.js
+++ b/src/structs/psconnection.js
@@ -1,4 +1,4 @@
-import {WebpackModules} from "modules";
+import {Logger, WebpackModules} from "modules";
const SortedGuildStore = WebpackModules.getByProps("getSortedGuilds");
const AvatarDefaults = WebpackModules.getByProps("getUserAvatarURL", "DEFAULT_AVATARS");
@@ -19,6 +19,8 @@ const betterDiscordServer = {
insertDate: 1517806800
};
+const ITEMS_PER_PAGE = 50;
+
export default new class PublicServersConnection {
constructor() {
@@ -43,9 +45,10 @@ export default new class PublicServersConnection {
return SortedGuildStore.getFlattenedGuildIds().includes(id);
}
- async search({term = "", keyword = "", from = 0} = {}) {
- if (this.cache.has(term + keyword + from)) return this.cache.get(term + keyword + from);
+ 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")}`);
@@ -55,19 +58,18 @@ export default new class PublicServersConnection {
try {
const response = await fetch(`${this.endPoint}${query}`, {method: "GET"});
const data = await response.json();
- const next = data.size + data.from;
const results = {
servers: data.results,
size: data.size,
- from: data.from,
total: data.total,
- next: next >= data.total ? null : next
+ page: Math.ceil(from / ITEMS_PER_PAGE) + 1,
+ numPages: Math.ceil(data.total / ITEMS_PER_PAGE)
};
- this.cache.set(term + keyword + from, results);
+ this.cache.set(term + keyword + page, results);
return results;
}
catch (error) {
- return null;
+ Logger.stacktrace("PublicServers", "Could not reach search endpoint.", error);
}
}
@@ -91,6 +93,7 @@ export default new class PublicServersConnection {
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")};
}
}
@@ -109,7 +112,8 @@ export default new class PublicServersConnection {
});
return true;
}
- catch (e) {
+ catch (error) {
+ Logger.warn("PublicServers", "Could not join server.");
return false;
}
}
@@ -130,6 +134,7 @@ export default new class PublicServersConnection {
return data;
}
catch (error) {
+ Logger.warn("PublicServers", "Could not verify connection.");
return false;
}
}
diff --git a/src/styles/builtins/publicservers.css b/src/styles/builtins/publicservers.css
index 9ebb8118..aa67887b 100644
--- a/src/styles/builtins/publicservers.css
+++ b/src/styles/builtins/publicservers.css
@@ -14,50 +14,6 @@
height: 24px;
}
-.bd-layer {
- -ms-flex-direction: column;
- -webkit-box-direction: normal;
- -webkit-box-orient: vertical;
- bottom: 0;
- display: -webkit-box;
- display: -ms-flexbox;
- display: flex;
- flex-direction: column;
- left: 0;
- position: absolute;
- right: 0;
- top: 0;
-}
-
-#pubslayer button {
- background: #7289da;
- color: #fff;
- font-size: 14px;
- font-weight: 500;
- line-height: 16px;
- padding: 2px 16px;
- border: none;
- border-radius: 3px;
- transition: background-color 0.17s ease;
-}
-
-#pubslayer button:hover {
- background-color: #677bc4;
-}
-
-#pubslayer input {
- color: #f6f6f7;
- background-color: rgba(0, 0, 0, 0.1);
- border-color: rgba(0, 0, 0, 0.3);
- padding: 10px;
- height: 30px;
- border-width: 1px;
- border-style: solid;
- border-radius: 3px;
- outline: none;
- transition: background-color 0.15s ease, border 0.15s ease;
-}
-
#bd-connection {
margin-left: 10px;
}
@@ -249,4 +205,40 @@
border-radius: 3px;
background: #3e82e5;
color: #fff;
+}
+
+.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 {
+ opacity: 1;
+}
+
+.bd-pagination button svg {
+ fill: var(--header-primary);
+}
+
+.bd-pagination button[disabled] {
+ opacity: 0.2;
+ cursor: not-allowed;
}
\ No newline at end of file
diff --git a/src/styles/index.css b/src/styles/index.css
index 7425a063..7e1a3483 100644
--- a/src/styles/index.css
+++ b/src/styles/index.css
@@ -3,6 +3,7 @@
@import "./builtins/*";
@import "./ui/*";
@import "./buttons.css";
+@import "./spinner.css";
.bd-chat-badge {
vertical-align: bottom;
diff --git a/src/styles/spinner.css b/src/styles/spinner.css
new file mode 100644
index 00000000..d2699f68
--- /dev/null
+++ b/src/styles/spinner.css
@@ -0,0 +1,43 @@
+.bd-spinner {
+ margin: 100px auto;
+ width: 57px;
+ height: 57px;
+ position: relative;
+}
+
+.bd-cube1,
+.bd-cube2 {
+ background-color: #3e82e5;
+ width: 15px;
+ height: 15px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ animation: bd-sk-cubemove 1.8s infinite ease-in-out;
+}
+
+.bd-cube2 {
+ animation-delay: -0.9s;
+}
+
+@keyframes bd-sk-cubemove {
+ 25% {
+ transform: translateX(42px) rotate(-90deg) scale(0.5);
+ }
+
+ 50% {
+ transform: translateX(42px) translateY(42px) rotate(-179deg);
+ }
+
+ 50.1% {
+ transform: translateX(42px) translateY(42px) rotate(-180deg);
+ }
+
+ 75% {
+ transform: translateX(0) translateY(42px) rotate(-270deg) scale(0.5);
+ }
+
+ 100% {
+ transform: rotate(-360deg);
+ }
+}
\ No newline at end of file
diff --git a/src/ui/icons/next.jsx b/src/ui/icons/next.jsx
new file mode 100644
index 00000000..50b1b993
--- /dev/null
+++ b/src/ui/icons/next.jsx
@@ -0,0 +1,11 @@
+import {React} from "modules";
+
+export default class ArrowRight extends React.Component {
+ render() {
+ const size = this.props.size || "24px";
+ return ;
+ }
+}
\ No newline at end of file
diff --git a/src/ui/icons/previous.jsx b/src/ui/icons/previous.jsx
new file mode 100644
index 00000000..913705df
--- /dev/null
+++ b/src/ui/icons/previous.jsx
@@ -0,0 +1,11 @@
+import {React} from "modules";
+
+export default class ArrowLeft extends React.Component {
+ render() {
+ const size = this.props.size || "24px";
+ return ;
+ }
+}
\ No newline at end of file
diff --git a/src/ui/publicservers/menu.js b/src/ui/publicservers/menu.js
index 17f53f16..242cab02 100644
--- a/src/ui/publicservers/menu.js
+++ b/src/ui/publicservers/menu.js
@@ -5,12 +5,21 @@ import ServerCard from "./card";
import EmptyResults from "./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.getByDisplayName("SettingsView");
const GuildActions = WebpackModules.getByProps("transitionToGuildSync");
const LayerManager = WebpackModules.getByProps("popLayer");
+const EMPTY_RESULTS = {
+ servers: [],
+ size: 0,
+ total: 0,
+ page: 0,
+ numPages: 0
+};
+
export default class PublicServers extends React.Component {
constructor(props) {
@@ -20,13 +29,7 @@ export default class PublicServers extends React.Component {
query: "",
loading: true,
user: null,
- results: {
- servers: [],
- size: 0,
- from: 0,
- total: 0,
- next: null
- }
+ results: Object.assign({}, EMPTY_RESULTS)
};
this.featured = [];
@@ -36,6 +39,7 @@ export default class PublicServers extends React.Component {
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);
@@ -62,7 +66,7 @@ export default class PublicServers extends React.Component {
this.setState({loading: false});
this.changeTab(this.state.tab);
- if (!this.keywords || !this.keywords.length) Modals.showConfirmationModal("Connection Error", "There was an error connecting to DiscordServers.com, it's possible their website/api is down. Please try again later.");
+ if (!this.keywords || !this.keywords.length) Modals.showConfirmationModal(Strings.PublicServers.connectionError, Strings.PublicServers.connectionErrorMessage);
}
async connect() {
@@ -77,18 +81,10 @@ export default class PublicServers extends React.Component {
else this.search(term);
}
- async search(term = "", from = 0) {
+ 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, from});
- if (!results) {
- return this.setState({results: {
- servers: [],
- size: 0,
- from: 0,
- total: 0,
- next: null
- }});
- }
+ 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});
}
@@ -100,18 +96,26 @@ export default class PublicServers extends React.Component {
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
+ page: 1,
+ numPages: 1
}});
}
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) return;
- this.search(this.state.query, this.state.results.next);
+ if (this.state.loading || !this.hasNext) return;
+ this.search(this.state.query, this.state.results.page + 1);
}
async join(id, native = false) {
@@ -133,17 +137,20 @@ export default class PublicServers extends React.Component {
}
get searchBox() {
- return