diff --git a/.prettierrc b/.prettierrc index 02eb701..8f6f119 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,7 +3,8 @@ "trailingComma": "all", "singleQuote": false, "printWidth": 100, - "useTabs": true, + "useTabs": false, + "tabWidth": 2, "bracketSpacing": true, "arrowParens": "always", "parser": "typescript" diff --git a/api/discord.js b/api/discord.js index 34a26c7..21b9423 100644 --- a/api/discord.js +++ b/api/discord.js @@ -12,81 +12,81 @@ const REDIRECT_URI = `${process.env.REDIRECT_DOMAIN}/api/discord/callback`; console.log("Redirect URI is: " + REDIRECT_URI); router.get("/login", (req, res) => { - res.redirect( - `https://discord.com/api/oauth2/authorize?client_id=${CLIENT_ID}&response_type=code&scope=identify&redirect_uri=${encodeURIComponent( - REDIRECT_URI, - )}`, - ); + res.redirect( + `https://discord.com/api/oauth2/authorize?client_id=${CLIENT_ID}&response_type=code&scope=identify&redirect_uri=${encodeURIComponent( + REDIRECT_URI, + )}`, + ); }); router.post("/refresh", (req, res) => { - // can be caused by a corrupted localStorage with no refresh token - // thus sending empty json - if (!req.body.refresh_token) { - res.status(400).json({ - message: "No refresh token provided", - }); - return; - } + // can be caused by a corrupted localStorage with no refresh token + // thus sending empty json + if (!req.body.refresh_token) { + res.status(400).json({ + message: "No refresh token provided", + }); + return; + } - const params = new URLSearchParams(); - params.append("client_id", CLIENT_ID); - params.append("client_secret", CLIENT_TOKEN); - params.append("grant_type", "refresh_token"); - params.append("refresh_token", req.body.refresh_token); + const params = new URLSearchParams(); + params.append("client_id", CLIENT_ID); + params.append("client_secret", CLIENT_TOKEN); + params.append("grant_type", "refresh_token"); + params.append("refresh_token", req.body.refresh_token); - fetch("https://discord.com/api/oauth2/token", { - method: "POST", - body: params, - }) - .then((response) => { - return response.json(); - }) - .then((json) => { - res.send(json); - }) - .catch((err) => { - res.status(400); - res.send(err); - }) - .finally(() => { - res.end(); - }); + fetch("https://discord.com/api/oauth2/token", { + method: "POST", + body: params, + }) + .then((response) => { + return response.json(); + }) + .then((json) => { + res.send(json); + }) + .catch((err) => { + res.status(400); + res.send(err); + }) + .finally(() => { + res.end(); + }); }); router.get("/callback", (req, res) => { - if (!req.query.code) { - res.status(400).send("No code given"); - return; - } + if (!req.query.code) { + res.status(400).send("No code given"); + return; + } - const params = new URLSearchParams(); - params.append("client_id", CLIENT_ID); - params.append("client_secret", CLIENT_TOKEN); - params.append("grant_type", "authorization_code"); - params.append("code", req.query.code); - params.append("redirect_uri", REDIRECT_URI); - params.append("scope", "identify"); + const params = new URLSearchParams(); + params.append("client_id", CLIENT_ID); + params.append("client_secret", CLIENT_TOKEN); + params.append("grant_type", "authorization_code"); + params.append("code", req.query.code); + params.append("redirect_uri", REDIRECT_URI); + params.append("scope", "identify"); - fetch("https://discord.com/api/oauth2/token", { - method: "POST", - body: params, - }) - .then((response) => response.json()) - .then((json) => { - res.redirect( - `/?access_token=${encodeURIComponent(json.access_token)}&refresh_token=${encodeURIComponent( - json.refresh_token, - )}&expires_in=${encodeURIComponent(json.expires_in)}`, - ); - }) - .catch((err) => { - res.status(400); - res.send(err); - }) - .finally(() => { - res.end(); - }); + fetch("https://discord.com/api/oauth2/token", { + method: "POST", + body: params, + }) + .then((response) => response.json()) + .then((json) => { + res.redirect( + `/?access_token=${encodeURIComponent(json.access_token)}&refresh_token=${encodeURIComponent( + json.refresh_token, + )}&expires_in=${encodeURIComponent(json.expires_in)}`, + ); + }) + .catch((err) => { + res.status(400); + res.send(err); + }) + .finally(() => { + res.end(); + }); }); module.exports = router; diff --git a/main.js b/main.js index 30e2a6a..25795d8 100644 --- a/main.js +++ b/main.js @@ -14,91 +14,91 @@ app.disable("x-powered-by"); const webappURL = "/"; process.on("unhandledRejection", (reason, promise) => { - console.error(reason); - console.trace(promise); + console.error(reason); + console.trace(promise); }); app.use( - express.urlencoded({ - extended: true, - limit: "50mb", - }), + express.urlencoded({ + extended: true, + limit: "50mb", + }), ); app.use(express.json({ limit: "50mb" })); app.get(webappURL, async (req, res) => { - let file = fs.readFileSync("./index.html", "utf8"); - - const WINDOW_ENV = { - DISCORD_USER_URL: process.env["DISCORD_USER_URL"] || undefined, - }; - - file = file.replace( - "", - ` \n`, - ); - - // change Vue to dev version for devtools - if (DEV) { - file = file.replace("/vue.min.js", "/vue.js"); - file = file.replace("vuetify.min.js", "vuetify.js"); - file = file.replace("/pinia.iife.min.js", "/pinia.iife.js"); - } - - if (DEV && process.env.BROWSER_REFRESH_URL) { - file = file.replace( - "", - ``, - ); - } - - let langs = await getLanguages().catch(errorHandler(res)); - - file = file.replace( - "", - "", - ); - - res.send(file); + let file = fs.readFileSync("./index.html", "utf8"); + + const WINDOW_ENV = { + DISCORD_USER_URL: process.env["DISCORD_USER_URL"] || undefined, + }; + + file = file.replace( + "", + ` \n`, + ); + + // change Vue to dev version for devtools + if (DEV) { + file = file.replace("/vue.min.js", "/vue.js"); + file = file.replace("vuetify.min.js", "vuetify.js"); + file = file.replace("/pinia.iife.min.js", "/pinia.iife.js"); + } + + if (DEV && process.env.BROWSER_REFRESH_URL) { + file = file.replace( + "", + ``, + ); + } + + let langs = await getLanguages().catch(errorHandler(res)); + + file = file.replace( + "", + "", + ); + + res.send(file); }); app.listen(port, () => { - console.log(`API at ${API_URL}`); - console.log(`Web App at http://localhost:${port}${webappURL}`); + console.log(`API at ${API_URL}`); + console.log(`Web App at http://localhost:${port}${webappURL}`); - if (DEV && process.send) { - process.send("online"); - } + if (DEV && process.send) { + process.send("online"); + } }); // https://www.techonthenet.com/js/language_tags.php const langPath = ["resources", "strings"]; const languagesPath = path.join(__dirname, ...langPath); const getLanguages = function () { - return fs.promises.readdir(languagesPath).then((files) => { - const result = files - .filter((f) => f.endsWith("js")) - .map((e) => { - const name = e.split(".").slice(0, -1).join("."); - return { - lang: name.includes("en") ? "en" : name.slice(-2).toLowerCase(), - bcp47: name.replace("_", "-"), - file: ["", ...langPath, e].join("/"), - }; - }); - - return result; - }); + return fs.promises.readdir(languagesPath).then((files) => { + const result = files + .filter((f) => f.endsWith("js")) + .map((e) => { + const name = e.split(".").slice(0, -1).join("."); + return { + lang: name.includes("en") ? "en" : name.slice(-2).toLowerCase(), + bcp47: name.replace("_", "-"), + file: ["", ...langPath, e].join("/"), + }; + }); + + return result; + }); }; app.use( - express.static(".", { - extensions: ["html", "xml", "json"], - }), + express.static(".", { + extensions: ["html", "xml", "json"], + }), ); app.use("/api/discord", require("./api/discord")); @@ -108,18 +108,18 @@ app.use("/api/discord", require("./api/discord")); * @return {Function} */ const errorHandler = function (res) { - return (err) => { - // advance parsing for axios errors and custom codes errors - const code = (err.response ? err.response.status : err.code) || 400; - const message = - (err.response && err.response.data ? err.response.data.error : err.message) || err; - - if (VERBOSE) { - console.error(code, message); - console.error(err.stack); - } - res.status(code); - res.send({ error: `${message}` }); - res.end(); - }; + return (err) => { + // advance parsing for axios errors and custom codes errors + const code = (err.response ? err.response.status : err.code) || 400; + const message = + (err.response && err.response.data ? err.response.data.error : err.message) || err; + + if (VERBOSE) { + console.error(code, message); + console.error(err.stack); + } + res.status(code); + res.send({ error: `${message}` }); + res.end(); + }; }; diff --git a/mixins/searchMixin.js b/mixins/searchMixin.js index c0a086e..6e6616f 100644 --- a/mixins/searchMixin.js +++ b/mixins/searchMixin.js @@ -1,53 +1,53 @@ export default { - methods: { - /** - * Loads all search params - * @returns {URLSearchParams} - */ - search_load() { - const query_str = location.hash.split("?")[1] || ""; - return new URLSearchParams(query_str); - }, - /** - * Gets a specific param - * @param {string} name Search param name - * @returns {String|null} param value - */ - search_get(name) { - return this.load().get(name); - }, - /** - * Updates search param with new - * @param {string} name Search param name - * @param {any} value given value - */ - search_set(name, value) { - const str_val = String(value); + methods: { + /** + * Loads all search params + * @returns {URLSearchParams} + */ + search_load() { + const query_str = location.hash.split("?")[1] || ""; + return new URLSearchParams(query_str); + }, + /** + * Gets a specific param + * @param {string} name Search param name + * @returns {String|null} param value + */ + search_get(name) { + return this.load().get(name); + }, + /** + * Updates search param with new + * @param {string} name Search param name + * @param {any} value given value + */ + search_set(name, value) { + const str_val = String(value); - const loaded = this.search_load(); - loaded.set(name, str_val); + const loaded = this.search_load(); + loaded.set(name, str_val); - this._search_update(loaded); - }, - search_delete(name) { - const loaded = this.search_load(); + this._search_update(loaded); + }, + search_delete(name) { + const loaded = this.search_load(); - loaded.delete(name); + loaded.delete(name); - this._search_update(loaded); - }, - /** - * update hash search - * @param {URLSearchParams} search_params updated params - */ - _search_update(search_params) { - let query_str = "?" + search_params.toString(); + this._search_update(loaded); + }, + /** + * update hash search + * @param {URLSearchParams} search_params updated params + */ + _search_update(search_params) { + let query_str = "?" + search_params.toString(); - let hash = location.hash; - if (hash.indexOf("?") !== -1) hash = hash.substring(0, hash.indexOf("?")); - hash += query_str; + let hash = location.hash; + if (hash.indexOf("?") !== -1) hash = hash.substring(0, hash.indexOf("?")); + hash += query_str; - location.hash = hash; - }, - }, + location.hash = hash; + }, + }, }; diff --git a/pages/addon/addonForm.js b/pages/addon/addonForm.js index 166efc7..47c253a 100644 --- a/pages/addon/addonForm.js +++ b/pages/addon/addonForm.js @@ -5,46 +5,46 @@ const FullscreenPreview = () => import("./fullscreen-preview.js"); const DropZone = () => import("../components/drop-zone.js"); export default { - name: "addon-form", - components: { - UserList, - ImagePreviewer, - FullscreenPreview, - DropZone, - }, - props: { - addonNew: { - type: Boolean, - required: true, - }, - loading: { - type: Boolean, - required: false, - default: () => false, - }, - addonData: { - required: false, - default: () => undefined, - }, - headerSource: { - required: false, - default: () => undefined, - }, - screenSources: { - required: false, - }, - screenIds: { - required: false, - type: Array, - default: undefined, - }, - disabledHeaderInput: { - required: false, - type: Boolean, - default: () => false, - }, - }, - template: ` + name: "addon-form", + components: { + UserList, + ImagePreviewer, + FullscreenPreview, + DropZone, + }, + props: { + addonNew: { + type: Boolean, + required: true, + }, + loading: { + type: Boolean, + required: false, + default: () => false, + }, + addonData: { + required: false, + default: () => undefined, + }, + headerSource: { + required: false, + default: () => undefined, + }, + screenSources: { + required: false, + }, + screenIds: { + required: false, + type: Array, + default: undefined, + }, + disabledHeaderInput: { + required: false, + type: Boolean, + default: () => false, + }, + }, + template: ` `, - data() { - return { - form: { - files: { - header: { - rules: [ - (header) => !!header || this.$root.lang().addons.images.header.rules.image_required, - (header) => - (header && header.size < this.form.files.header.counter.max) || - this.$root - .lang() - .addons.images.header.rules.image_size.replace( - "%s", - this.form.files.header.counter.max / 1000, - ), - ], - counter: { - max: 3000000, - }, - }, - carousel: { - rules: [ - (files) => { - return ( - files - .map( - (file) => - file.size < this.form.files.carousel.counter.max || - this.$root - .lang() - .addons.images.header.rules.image_size.replace( - "%s", - this.form.files.header.counter.max / 1000, - ), - ) - .filter((r) => typeof r === "string")[0] || true - ); - }, - ], - counter: { - max: 3000000, - }, - }, - value: "", - }, - description: { - rules: [ - (desc) => - !!desc || this.$root.lang().addons.general.description.rules.description_required, - (desc) => - (desc && desc.length <= this.form.description.counter.max) || - this.$root - .lang() - .addons.general.description.rules.description_too_big.replace( - "%s", - this.form.description.counter.max, - ), - (desc) => - (desc && desc.length >= this.form.description.counter.min) || - this.$root - .lang() - .addons.general.description.rules.description_too_small.replace( - "%s", - this.form.description.counter.min, - ), - ], - counter: { - min: 32, - max: 4096, - }, - }, - embed_description: { - rules: [ - (desc) => - (desc && desc.length > this.form.embed_description.counter.max) || - this.$root - .lang() - .addons.general.embed_description.rules.too_big.replace( - "%s", - this.form.embed_description.counter.max, - ), - ], - counter: { - max: 160, - }, - }, - name: { - rules: [ - (name) => !!name || this.$root.lang().addons.general.name.rules.name_required, - (name) => - (name && name.length <= this.form.name.counter.max) || - this.$root - .lang() - .addons.general.name.rules.name_too_big.replace("%s", this.form.name.counter.max), - (name) => - (name && name.length >= this.form.name.counter.min) || - this.$root - .lang() - .addons.general.name.rules.name_too_small.replace("%s", this.form.name.counter.min), - ], - counter: { - min: 5, - max: 30, - }, - }, - slug: { - rules: [ - (input) => !!input || this.$root.lang().addons.general.slug.rules.required, - (input) => - (input && input.length <= this.form.slug.counter.max) || - this.$root - .lang() - .addons.general.slug.rules.too_big.replace("%s", this.form.slug.counter.max), - (input) => - (input && input.length >= this.form.slug.counter.min) || - this.$root - .lang() - .addons.general.slug.rules.too_small.replace("%s", this.form.slug.counter.min), - (input) => - /^[a-zA-Z0-9\-]+$/.test(input) || - this.$root.lang().addons.general.slug.rules.incorrect_format, - ], - counter: { - min: 5, - max: 30, - }, - }, - }, - submittedForm: { - name: "", - headerFile: undefined, - carouselFiles: [], - description: "", - downloads: [ - { - key: "", - links: [""], - }, - ], - authors: [], - selectedEditions: ["Java"], - selectedRes: ["32x"], - options: { - tags: [], - comments: true, - optifine: false, - }, - }, - headerValid: false, - headerValidating: false, - headerError: "", - carouselValid: true, - carouselValidating: false, - carouselError: "", - carouselDoNotVerify: false, - downloadTitleRules: [ - (u) => !!u || this.$root.lang().addons.downloads.name.rules.name_required, - (u) => u !== " " || this.$root.lang().addons.downloads.name.rules.name_cannot_be_empty, - ], - downloadLinkRules: [(u) => this.validURL(u) || this.$root.lang().addons.downloads.link.rule], - validForm: false, - editions: ["Java", "Bedrock"], - res: ["32x", "64x"], - }; - }, - computed: { - hasHeader() { - return !!(this.header || this.headerURL); - }, - header() { - return this.addonNew - ? this.headerValidating == false && this.headerValid && this.submittedForm.headerFile - ? URL.createObjectURL(this.submittedForm.headerFile) - : undefined - : this.headerSource - ? this.headerSource - : undefined; - }, - carouselSources() { - return this.screenSources ? this.screenSources : []; - }, - headerValidSentence() { - if (this.headerValidating) { - return "Header being verified..."; - } else if (this.headerValid) { - return true; - } - - return this.headerError; - }, - headerRules() { - if (!this.addonNew) return []; - return [...this.form.files.header.rules, this.headerValidSentence]; - }, - headerFile() { - return this.submittedForm.headerFile; - }, - carouselFiles() { - return this.submittedForm.carouselFiles; - }, - carouselRules() { - return [...this.form.files.carousel.rules, this.carouselValidSentence]; - }, - carouselValidSentence() { - if (this.carouselValidating) { - return "Carousel being verified..."; - } else if (this.carouselValid) { - return true; - } - - return this.carouselError; - }, - submittedData() { - let res = Object.merge({}, this.submittedForm); - - res.options.tags = [...res.selectedEditions, ...res.selectedRes]; - delete res.selectedEditions; - delete res.selectedRes; - - // we treat files with different endpoint - delete res.headerFile; - delete res.carouselFiles; - - return res; - }, - }, - methods: { - carouselChange() { - if (this.carouselDoNotVerify) return; - - const files = this.submittedForm.carouselFiles; - if (!files || files.length == 0) return; - - this.carouselValidating = true; - Promise.all(files.map((f) => this.verifyImage(f, this.validateRatio))) - .then(() => { - this.carouselValid = true; - this.$emit("screenshot", files); - this.submittedForm.carouselFiles = []; - }) - .catch((error) => { - console.error(error); - this.carouselValid = false; - this.carouselError = error.message; - this.$root.showSnackBar(error, "error"); - }) - .finally(() => { - this.carouselValidating = false; - }); - }, - headerChange(file) { - // delete not uploaded file - if (!file) { - if (this.addonNew) { - this.$emit("header", undefined, true); - } - return; - } - - // activate validation loading - this.headerValidating = true; - - this.verifyImage(file, this.validateRatio) - .then(() => { - this.headerValid = true; - this.$emit("header", file); - if (!this.addonNew) { - this.submittedForm.headerFile = undefined; - } - }) - .catch((error) => { - this.headerValid = false; - this.headerError = error.message; - console.error(error); - - // input is changed so we delete parent component value - if (this.addonNew) this.$emit("header", undefined, true); - // if not addon new will delete file - - this.$root.showSnackBar(error.message, "error"); - }) - .finally(() => { - this.headerValidating = false; - }); - }, - downloadAdd() { - this.submittedForm.downloads.push({ key: "", links: [""] }); - }, - downloadRemove(download_index) { - this.submittedForm.downloads.splice(download_index, 1); - }, - linkAdd(download_index) { - this.submittedForm.downloads[download_index].links.push(""); - }, - linkRemove(download_index, link_index) { - this.submittedForm.downloads[download_index].links.splice(link_index, 1); - }, - onDeleteCarousel(item, index, id) { - this.carouselDoNotVerify = true; - this.submittedForm.carouselFiles.splice(index, 1); - this.$emit("screenshot", undefined, index, true, id); - Vue.nextTick(() => { - this.carouselDoNotVerify = false; - }); - }, - onSubmit(approve = false) { - const valid = this.$refs.form.validate(); - - if (!valid) return; - - this.$emit("submit", this.submittedData, approve); - }, - validateRatio(ctx) { - const ratio = (ctx.width / ctx.height).toFixed(2) == 1.78; - if (!ratio) throw new Error(this.$root.lang().addons.images.header.rules.image_ratio); - }, - validURL(str) { - const pattern = new RegExp( - "^(https?:\\/\\/)?" + // protocol - "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name - "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address - "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path - "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string - "(\\#[-a-z\\d_]*)?$", - "i", - ); // fragment locator - return !!pattern.test(str); - }, - verifyImage(file, validateImage) { - if (validateImage === undefined) validateImage = this.validateRatio; - - return new Promise((resolve, reject) => { - // start reader - const reader = new FileReader(); - - reader.onload = function (e) { - const image = new Image(); - image.src = e.target.result; - image.onload = function () { - try { - validateImage(this); - resolve(); - } catch (error) { - reject(error); - } - }; - image.onerror = function (error) { - reject(e); - }; - }; - reader.onerror = function (error) { - reject(e); - }; - - // set file to be read - reader.readAsDataURL(file); - }); - }, - }, - watch: { - addonData: { - handler(data) { - if (!this.addonNew && data) { - data = JSON.parse(JSON.stringify(data)); - data.headerFile = undefined; - data.carouselFiles = []; - data.selectedRes = data.options.tags.filter((e) => this.res.includes(e)); - data.selectedEditions = data.options.tags.filter((e) => this.editions.includes(e)); - this.submittedForm = data; - } - }, - immediate: true, - deep: true, - }, - }, - beforeMount() { - this.submittedForm.authors = [this.$root.user.id]; - }, + data() { + return { + form: { + files: { + header: { + rules: [ + (header) => !!header || this.$root.lang().addons.images.header.rules.image_required, + (header) => + (header && header.size < this.form.files.header.counter.max) || + this.$root + .lang() + .addons.images.header.rules.image_size.replace( + "%s", + this.form.files.header.counter.max / 1000, + ), + ], + counter: { + max: 3000000, + }, + }, + carousel: { + rules: [ + (files) => { + return ( + files + .map( + (file) => + file.size < this.form.files.carousel.counter.max || + this.$root + .lang() + .addons.images.header.rules.image_size.replace( + "%s", + this.form.files.header.counter.max / 1000, + ), + ) + .filter((r) => typeof r === "string")[0] || true + ); + }, + ], + counter: { + max: 3000000, + }, + }, + value: "", + }, + description: { + rules: [ + (desc) => + !!desc || this.$root.lang().addons.general.description.rules.description_required, + (desc) => + (desc && desc.length <= this.form.description.counter.max) || + this.$root + .lang() + .addons.general.description.rules.description_too_big.replace( + "%s", + this.form.description.counter.max, + ), + (desc) => + (desc && desc.length >= this.form.description.counter.min) || + this.$root + .lang() + .addons.general.description.rules.description_too_small.replace( + "%s", + this.form.description.counter.min, + ), + ], + counter: { + min: 32, + max: 4096, + }, + }, + embed_description: { + rules: [ + (desc) => + (desc && desc.length > this.form.embed_description.counter.max) || + this.$root + .lang() + .addons.general.embed_description.rules.too_big.replace( + "%s", + this.form.embed_description.counter.max, + ), + ], + counter: { + max: 160, + }, + }, + name: { + rules: [ + (name) => !!name || this.$root.lang().addons.general.name.rules.name_required, + (name) => + (name && name.length <= this.form.name.counter.max) || + this.$root + .lang() + .addons.general.name.rules.name_too_big.replace("%s", this.form.name.counter.max), + (name) => + (name && name.length >= this.form.name.counter.min) || + this.$root + .lang() + .addons.general.name.rules.name_too_small.replace("%s", this.form.name.counter.min), + ], + counter: { + min: 5, + max: 30, + }, + }, + slug: { + rules: [ + (input) => !!input || this.$root.lang().addons.general.slug.rules.required, + (input) => + (input && input.length <= this.form.slug.counter.max) || + this.$root + .lang() + .addons.general.slug.rules.too_big.replace("%s", this.form.slug.counter.max), + (input) => + (input && input.length >= this.form.slug.counter.min) || + this.$root + .lang() + .addons.general.slug.rules.too_small.replace("%s", this.form.slug.counter.min), + (input) => + /^[a-zA-Z0-9\-]+$/.test(input) || + this.$root.lang().addons.general.slug.rules.incorrect_format, + ], + counter: { + min: 5, + max: 30, + }, + }, + }, + submittedForm: { + name: "", + headerFile: undefined, + carouselFiles: [], + description: "", + downloads: [ + { + key: "", + links: [""], + }, + ], + authors: [], + selectedEditions: ["Java"], + selectedRes: ["32x"], + options: { + tags: [], + comments: true, + optifine: false, + }, + }, + headerValid: false, + headerValidating: false, + headerError: "", + carouselValid: true, + carouselValidating: false, + carouselError: "", + carouselDoNotVerify: false, + downloadTitleRules: [ + (u) => !!u || this.$root.lang().addons.downloads.name.rules.name_required, + (u) => u !== " " || this.$root.lang().addons.downloads.name.rules.name_cannot_be_empty, + ], + downloadLinkRules: [(u) => this.validURL(u) || this.$root.lang().addons.downloads.link.rule], + validForm: false, + editions: ["Java", "Bedrock"], + res: ["32x", "64x"], + }; + }, + computed: { + hasHeader() { + return !!(this.header || this.headerURL); + }, + header() { + return this.addonNew + ? this.headerValidating == false && this.headerValid && this.submittedForm.headerFile + ? URL.createObjectURL(this.submittedForm.headerFile) + : undefined + : this.headerSource + ? this.headerSource + : undefined; + }, + carouselSources() { + return this.screenSources ? this.screenSources : []; + }, + headerValidSentence() { + if (this.headerValidating) { + return "Header being verified..."; + } else if (this.headerValid) { + return true; + } + + return this.headerError; + }, + headerRules() { + if (!this.addonNew) return []; + return [...this.form.files.header.rules, this.headerValidSentence]; + }, + headerFile() { + return this.submittedForm.headerFile; + }, + carouselFiles() { + return this.submittedForm.carouselFiles; + }, + carouselRules() { + return [...this.form.files.carousel.rules, this.carouselValidSentence]; + }, + carouselValidSentence() { + if (this.carouselValidating) { + return "Carousel being verified..."; + } else if (this.carouselValid) { + return true; + } + + return this.carouselError; + }, + submittedData() { + let res = Object.merge({}, this.submittedForm); + + res.options.tags = [...res.selectedEditions, ...res.selectedRes]; + delete res.selectedEditions; + delete res.selectedRes; + + // we treat files with different endpoint + delete res.headerFile; + delete res.carouselFiles; + + return res; + }, + }, + methods: { + carouselChange() { + if (this.carouselDoNotVerify) return; + + const files = this.submittedForm.carouselFiles; + if (!files || files.length == 0) return; + + this.carouselValidating = true; + Promise.all(files.map((f) => this.verifyImage(f, this.validateRatio))) + .then(() => { + this.carouselValid = true; + this.$emit("screenshot", files); + this.submittedForm.carouselFiles = []; + }) + .catch((error) => { + console.error(error); + this.carouselValid = false; + this.carouselError = error.message; + this.$root.showSnackBar(error, "error"); + }) + .finally(() => { + this.carouselValidating = false; + }); + }, + headerChange(file) { + // delete not uploaded file + if (!file) { + if (this.addonNew) { + this.$emit("header", undefined, true); + } + return; + } + + // activate validation loading + this.headerValidating = true; + + this.verifyImage(file, this.validateRatio) + .then(() => { + this.headerValid = true; + this.$emit("header", file); + if (!this.addonNew) { + this.submittedForm.headerFile = undefined; + } + }) + .catch((error) => { + this.headerValid = false; + this.headerError = error.message; + console.error(error); + + // input is changed so we delete parent component value + if (this.addonNew) this.$emit("header", undefined, true); + // if not addon new will delete file + + this.$root.showSnackBar(error.message, "error"); + }) + .finally(() => { + this.headerValidating = false; + }); + }, + downloadAdd() { + this.submittedForm.downloads.push({ key: "", links: [""] }); + }, + downloadRemove(download_index) { + this.submittedForm.downloads.splice(download_index, 1); + }, + linkAdd(download_index) { + this.submittedForm.downloads[download_index].links.push(""); + }, + linkRemove(download_index, link_index) { + this.submittedForm.downloads[download_index].links.splice(link_index, 1); + }, + onDeleteCarousel(item, index, id) { + this.carouselDoNotVerify = true; + this.submittedForm.carouselFiles.splice(index, 1); + this.$emit("screenshot", undefined, index, true, id); + Vue.nextTick(() => { + this.carouselDoNotVerify = false; + }); + }, + onSubmit(approve = false) { + const valid = this.$refs.form.validate(); + + if (!valid) return; + + this.$emit("submit", this.submittedData, approve); + }, + validateRatio(ctx) { + const ratio = (ctx.width / ctx.height).toFixed(2) == 1.78; + if (!ratio) throw new Error(this.$root.lang().addons.images.header.rules.image_ratio); + }, + validURL(str) { + const pattern = new RegExp( + "^(https?:\\/\\/)?" + // protocol + "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name + "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address + "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path + "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string + "(\\#[-a-z\\d_]*)?$", + "i", + ); // fragment locator + return !!pattern.test(str); + }, + verifyImage(file, validateImage) { + if (validateImage === undefined) validateImage = this.validateRatio; + + return new Promise((resolve, reject) => { + // start reader + const reader = new FileReader(); + + reader.onload = function (e) { + const image = new Image(); + image.src = e.target.result; + image.onload = function () { + try { + validateImage(this); + resolve(); + } catch (error) { + reject(error); + } + }; + image.onerror = function (error) { + reject(e); + }; + }; + reader.onerror = function (error) { + reject(e); + }; + + // set file to be read + reader.readAsDataURL(file); + }); + }, + }, + watch: { + addonData: { + handler(data) { + if (!this.addonNew && data) { + data = JSON.parse(JSON.stringify(data)); + data.headerFile = undefined; + data.carouselFiles = []; + data.selectedRes = data.options.tags.filter((e) => this.res.includes(e)); + data.selectedEditions = data.options.tags.filter((e) => this.editions.includes(e)); + this.submittedForm = data; + } + }, + immediate: true, + deep: true, + }, + }, + beforeMount() { + this.submittedForm.authors = [this.$root.user.id]; + }, }; diff --git a/pages/addon/editAddonForm.js b/pages/addon/editAddonForm.js index 54bf647..6819aaa 100644 --- a/pages/addon/editAddonForm.js +++ b/pages/addon/editAddonForm.js @@ -1,11 +1,11 @@ const addonForm = () => import("./addonForm.js"); export default { - name: "edit-addon-form", - components: { - "addon-form": addonForm, - }, - template: ` + name: "edit-addon-form", + components: { + "addon-form": addonForm, + }, + template: `

{{ $root.lang().addons.titles.edit }} #{{this.id}}

`, - data() { - return { - hidisabled: false, - reasonDialog: false, - reasonData: undefined, - reasonRules: [ - () => - !!(this.reason && this.reason.trim()) || - this.$root.lang("addons.general.reason.required"), - () => - this.reason.trim().length < this.reasonCounter.min || - this.reason.trim().length > this.reasonCounter.max - ? this.$root - .lang("addons.general.reason.bounds") - .replace("%s", this.reasonCounter.min) - .replace("%s", this.reasonCounter.max) - : true, - ], - reasonCounter: { - min: 5, - max: 150, - }, - reason: "", - validForm: false, - addonData: undefined, - headerSource: undefined, - screenSources: [], - screenIds: [], - }; - }, - computed: { - loading() { - return this.addonData === undefined; - }, - id() { - return this.$route.params.id; - }, - }, - methods: { - handleReasonDialog(submitted) { - const valid = this.$refs.reasonForm.validate(); - if (!valid) return; + data() { + return { + hidisabled: false, + reasonDialog: false, + reasonData: undefined, + reasonRules: [ + () => + !!(this.reason && this.reason.trim()) || + this.$root.lang("addons.general.reason.required"), + () => + this.reason.trim().length < this.reasonCounter.min || + this.reason.trim().length > this.reasonCounter.max + ? this.$root + .lang("addons.general.reason.bounds") + .replace("%s", this.reasonCounter.min) + .replace("%s", this.reasonCounter.max) + : true, + ], + reasonCounter: { + min: 5, + max: 150, + }, + reason: "", + validForm: false, + addonData: undefined, + headerSource: undefined, + screenSources: [], + screenIds: [], + }; + }, + computed: { + loading() { + return this.addonData === undefined; + }, + id() { + return this.$route.params.id; + }, + }, + methods: { + handleReasonDialog(submitted) { + const valid = this.$refs.reasonForm.validate(); + if (!valid) return; - this.reasonDialog = false; - if (submitted) { - this.confirmSubmit(this.reasonData, false); - } else { - this.reason = ""; - } - }, - handleSubmit(data, approve) { - if (!approve) { - this.reasonData = data; - this.reasonDialog = true; - } else { - this.confirmSubmit(data, approve); - } - }, - confirmSubmit(data, approve) { - if (approve) { - data.reason = "Admin edit"; - } else { - data.reason = this.reason.trim(); - this.reason = ""; - } - let prom = axios - .patch(this.$root.apiURL + "/addons/" + this.id, data, this.$root.apiOptions) - .then(() => { - this.$root.showSnackBar("Saved", "success"); - }); + this.reasonDialog = false; + if (submitted) { + this.confirmSubmit(this.reasonData, false); + } else { + this.reason = ""; + } + }, + handleSubmit(data, approve) { + if (!approve) { + this.reasonData = data; + this.reasonDialog = true; + } else { + this.confirmSubmit(data, approve); + } + }, + confirmSubmit(data, approve) { + if (approve) { + data.reason = "Admin edit"; + } else { + data.reason = this.reason.trim(); + this.reason = ""; + } + let prom = axios + .patch(this.$root.apiURL + "/addons/" + this.id, data, this.$root.apiOptions) + .then(() => { + this.$root.showSnackBar("Saved", "success"); + }); - if (approve === true) { - prom = prom - .then(() => { - return axios.put( - this.$root.apiURL + "/addons/" + this.id + "/review", - { - status: "approved", - reason: "Admin edit", - }, - this.$root.apiOptions, - ); - }) - .then(() => { - this.$root.showSnackBar("Approved", "success"); - }); - } + if (approve === true) { + prom = prom + .then(() => { + return axios.put( + this.$root.apiURL + "/addons/" + this.id + "/review", + { + status: "approved", + reason: "Admin edit", + }, + this.$root.apiOptions, + ); + }) + .then(() => { + this.$root.showSnackBar("Approved", "success"); + }); + } - prom.catch((err) => { - this.$root.showSnackBar(err, "error"); - }); - }, - handleHeader(file, remove = false) { - this.hidisabled = true; + prom.catch((err) => { + this.$root.showSnackBar(err, "error"); + }); + }, + handleHeader(file, remove = false) { + this.hidisabled = true; - let promise; - if (remove) { - promise = axios.delete( - this.$root.apiURL + "/addons/" + this.id + "/header", - this.$root.apiOptions, - ); - } else { - const form = new FormData(); - form.set("file", file, file.name); - promise = axios.post( - this.$root.apiURL + "/addons/" + this.id + "/header", - form, - this.$root.apiOptions, - ); - } + let promise; + if (remove) { + promise = axios.delete( + this.$root.apiURL + "/addons/" + this.id + "/header", + this.$root.apiOptions, + ); + } else { + const form = new FormData(); + form.set("file", file, file.name); + promise = axios.post( + this.$root.apiURL + "/addons/" + this.id + "/header", + form, + this.$root.apiOptions, + ); + } - promise - .then(() => { - this.getHeader(); - this.$root.showSnackBar( - "Successfully " + (remove ? "removed" : "uploaded") + " header image", - "success", - ); - }) - .catch((err) => { - console.error(err); - this.$root.showSnackBar(err, "error"); - }) - .finally(() => { - this.hidisabled = false; - }); - }, - getHeader() { - axios({ - method: "GET", - url: this.$root.apiURL + "/addons/" + this.id + "/files/header", - ...this.$root.apiOptions, - }) - .then((res) => { - const url = res.data + "?t=" + new Date().getTime(); - this.headerSource = url; - }) - .catch(() => { - this.headerSource = undefined; - }); - }, - async handleScreenshot(screenshots, index, remove = false, id) { - if (Array.isArray(screenshots) && screenshots.length === 0) return; + promise + .then(() => { + this.getHeader(); + this.$root.showSnackBar( + "Successfully " + (remove ? "removed" : "uploaded") + " header image", + "success", + ); + }) + .catch((err) => { + console.error(err); + this.$root.showSnackBar(err, "error"); + }) + .finally(() => { + this.hidisabled = false; + }); + }, + getHeader() { + axios({ + method: "GET", + url: this.$root.apiURL + "/addons/" + this.id + "/files/header", + ...this.$root.apiOptions, + }) + .then((res) => { + const url = res.data + "?t=" + new Date().getTime(); + this.headerSource = url; + }) + .catch(() => { + this.headerSource = undefined; + }); + }, + async handleScreenshot(screenshots, index, remove = false, id) { + if (Array.isArray(screenshots) && screenshots.length === 0) return; - let promise; - if (remove) { - if (id !== undefined) { - promise = axios.delete( - this.$root.apiURL + "/addons/" + this.id + "/screenshots/" + id, - this.$root.apiOptions, - ); - } else { - promise = axios.delete( - this.$root.apiURL + "/addons/" + this.id + "/screenshots/" + index, - this.$root.apiOptions, - ); - } - } else { - // add all of them - // fix to stabilize upload and make one request then another... - let i = 0; - let successful = true; - let err; - while (i < screenshots.length && successful) { - const screen = screenshots[i]; - const form = new FormData(); - form.set("file", screen, screen.name); + let promise; + if (remove) { + if (id !== undefined) { + promise = axios.delete( + this.$root.apiURL + "/addons/" + this.id + "/screenshots/" + id, + this.$root.apiOptions, + ); + } else { + promise = axios.delete( + this.$root.apiURL + "/addons/" + this.id + "/screenshots/" + index, + this.$root.apiOptions, + ); + } + } else { + // add all of them + // fix to stabilize upload and make one request then another... + let i = 0; + let successful = true; + let err; + while (i < screenshots.length && successful) { + const screen = screenshots[i]; + const form = new FormData(); + form.set("file", screen, screen.name); - successful = await axios - .post( - this.$root.apiURL + "/addons/" + this.id + "/screenshots", - form, - this.$root.apiOptions, - ) - .then(() => true) - .catch((error) => { - err = error; - return false; - }); + successful = await axios + .post( + this.$root.apiURL + "/addons/" + this.id + "/screenshots", + form, + this.$root.apiOptions, + ) + .then(() => true) + .catch((error) => { + err = error; + return false; + }); - i++; - } + i++; + } - promise = successful ? Promise.resolve() : Promise.reject(err); - } + promise = successful ? Promise.resolve() : Promise.reject(err); + } - promise - .then(() => { - this.getScreens(); - this.$root.showSnackBar( - "Successfully " + (remove ? "removed" : "uploaded") + " screenshots", - "success", - ); - }) - .catch((err) => { - console.error(err); - this.$root.showSnackBar(err, "error"); - }); - }, - getScreens() { - axios - .get( - this.$root.apiURL + - "/addons/" + - (this.id || this.$route.params.id) + - "/files/screenshots", - this.$root.apiOptions, - ) - .then((res) => { - this.screenSources = res.data; - }); - axios - .get( - this.$root.apiURL + - "/addons/" + - (this.id || this.$route.params.id) + - "/files/screenshots-ids", - this.$root.apiOptions, - ) - .then((res) => { - this.screenIds = res.data; - }); - }, - }, - created() { - this.getHeader(); - this.getScreens(); - Promise.all([ - axios.get(this.$root.apiURL + "/addons/" + this.$route.params.id, this.$root.apiOptions), - axios.get( - this.$root.apiURL + "/addons/" + this.$route.params.id + "/files/downloads", - this.$root.apiOptions, - ), - ]).then((res) => { - let addon_loaded = { - ...res[0].data, - downloads: res[1].data, - }; - delete addon_loaded.last_updated; - delete addon_loaded.slug; - delete addon_loaded.approval; - delete addon_loaded.id; + promise + .then(() => { + this.getScreens(); + this.$root.showSnackBar( + "Successfully " + (remove ? "removed" : "uploaded") + " screenshots", + "success", + ); + }) + .catch((err) => { + console.error(err); + this.$root.showSnackBar(err, "error"); + }); + }, + getScreens() { + axios + .get( + this.$root.apiURL + + "/addons/" + + (this.id || this.$route.params.id) + + "/files/screenshots", + this.$root.apiOptions, + ) + .then((res) => { + this.screenSources = res.data; + }); + axios + .get( + this.$root.apiURL + + "/addons/" + + (this.id || this.$route.params.id) + + "/files/screenshots-ids", + this.$root.apiOptions, + ) + .then((res) => { + this.screenIds = res.data; + }); + }, + }, + created() { + this.getHeader(); + this.getScreens(); + Promise.all([ + axios.get(this.$root.apiURL + "/addons/" + this.$route.params.id, this.$root.apiOptions), + axios.get( + this.$root.apiURL + "/addons/" + this.$route.params.id + "/files/downloads", + this.$root.apiOptions, + ), + ]).then((res) => { + let addon_loaded = { + ...res[0].data, + downloads: res[1].data, + }; + delete addon_loaded.last_updated; + delete addon_loaded.slug; + delete addon_loaded.approval; + delete addon_loaded.id; - this.addonData = addon_loaded; - }); - }, + this.addonData = addon_loaded; + }); + }, }; diff --git a/pages/addon/fullscreen-preview.js b/pages/addon/fullscreen-preview.js index 335cc6a..d15dbbd 100644 --- a/pages/addon/fullscreen-preview.js +++ b/pages/addon/fullscreen-preview.js @@ -1,6 +1,6 @@ export default { - name: "fullscreen-preview", - template: ` + name: "fullscreen-preview", + template: ` @@ -14,27 +14,27 @@ export default { `, - props: { - aspectRatio: { - required: false, - type: Number, - default: () => 16 / 9, - }, - src: { - required: true, - }, - }, - data() { - return { - fullscreen: false, - }; - }, - methods: { - close() { - this.fullscreen = false; - }, - open() { - this.fullscreen = true; - }, - }, + props: { + aspectRatio: { + required: false, + type: Number, + default: () => 16 / 9, + }, + src: { + required: true, + }, + }, + data() { + return { + fullscreen: false, + }; + }, + methods: { + close() { + this.fullscreen = false; + }, + open() { + this.fullscreen = true; + }, + }, }; diff --git a/pages/addon/image-previewer.js b/pages/addon/image-previewer.js index fcf2332..ec6c55d 100644 --- a/pages/addon/image-previewer.js +++ b/pages/addon/image-previewer.js @@ -1,11 +1,11 @@ const FullscreenPreview = () => import("./fullscreen-preview.js"); export default { - name: `image-previewer`, - components: { - FullscreenPreview, - }, - template: ` + name: `image-previewer`, + components: { + FullscreenPreview, + }, + template: `
@@ -36,50 +36,50 @@ export default { /> `, - props: { - sources: { - required: true, - type: Array, - }, - ids: { - required: false, - type: Array, - default: undefined, - }, - deletable: { - required: false, - type: Boolean, - default: true, - }, - }, - data() { - return { - fullscreenIndex: undefined, - }; - }, - computed: { - fullscreenItem() { - if (this.fullscreenIndex === undefined) return undefined; - return this.sources[this.fullscreenIndex]; - }, - notEmpty() { - return this.sources && !!this.sources.length; - }, - }, - methods: { - onDelete(item, index, e) { - if (e) e.target.blur(); + props: { + sources: { + required: true, + type: Array, + }, + ids: { + required: false, + type: Array, + default: undefined, + }, + deletable: { + required: false, + type: Boolean, + default: true, + }, + }, + data() { + return { + fullscreenIndex: undefined, + }; + }, + computed: { + fullscreenItem() { + if (this.fullscreenIndex === undefined) return undefined; + return this.sources[this.fullscreenIndex]; + }, + notEmpty() { + return this.sources && !!this.sources.length; + }, + }, + methods: { + onDelete(item, index, e) { + if (e) e.target.blur(); - if (this.ids !== undefined) { - this.$emit("item-delete", item, index, this.ids[index]); - } else { - this.$emit("item-delete", item, index, undefined); - } - }, - onFullscreen(item, index, e) { - if (e) e.target.blur(); - this.fullscreenIndex = index; - this.$refs.preview.open(); - }, - }, + if (this.ids !== undefined) { + this.$emit("item-delete", item, index, this.ids[index]); + } else { + this.$emit("item-delete", item, index, undefined); + } + }, + onFullscreen(item, index, e) { + if (e) e.target.blur(); + this.fullscreenIndex = index; + this.$refs.preview.open(); + }, + }, }; diff --git a/pages/addon/newAddonForm.js b/pages/addon/newAddonForm.js index 153b905..a7e7b89 100644 --- a/pages/addon/newAddonForm.js +++ b/pages/addon/newAddonForm.js @@ -1,11 +1,11 @@ const addonForm = () => import("./addonForm.js"); export default { - name: "new-addon-form", - components: { - "addon-form": addonForm, - }, - template: ` + name: "new-addon-form", + components: { + "addon-form": addonForm, + }, + template: `

{{ $root.lang().addons.titles.submit }}

`, - data() { - return { - header: undefined, - screenshots: [], - screenshotIds: [], - screenshotId: 0, - }; - }, - computed: { - screenSources() { - return this.screenshots.map((file) => URL.createObjectURL(file)); - }, - }, - methods: { - handleSubmit(data) { - // 1. Upload - let id; - axios - .post(this.$root.apiURL + "/addons", data, this.$root.apiOptions) - .then(async (response) => { - const addon = response.data; - id = addon.id; - - const promises = []; - // 2. Upload header and screenshots - let form; - if (this.header || this.screenshots.length) { - form = new FormData(); - } - - if (this.header) { - form.set("file", this.header, this.header.name); - promises.push( - axios.post( - this.$root.apiURL + "/addons/" + id + "/header", - form, - this.$root.apiOptions, - ), - ); - } - if (this.screenshots.length) { - // add all of them - // fix to stabilize upload and make one request then another... - let i = 0; - let successful = true; - let err; - let screenshots = this.screenshots; - while (i < screenshots.length && successful) { - const screen = screenshots[i]; - const form = new FormData(); - form.set("file", screen, screen.name); - - successful = await axios - .post( - this.$root.apiURL + "/addons/" + id + "/screenshots", - form, - this.$root.apiOptions, - ) - .then(() => true) - .catch((error) => { - err = error; - return false; - }); - - i++; - } - - promises.push(successful ? Promise.resolve() : Promise.reject(err)); - } - - return Promise.all(promises); - }) - .then(() => { - this.$root.showSnackBar("Saved", "success"); - this.$router.push("/addons/submissions"); - }) - .catch((err) => { - console.error(err); - this.$root.showSnackBar(err, "error"); - - // delete what is left of addon - // if we have id then we at least successfully created the file - if (id) - axios - .delete(this.$root.apiURL + "/addons/" + id, this.$root.apiOptions) - .catch((err) => { - this.$root.showSnackBar(err, "error"); - }); - }); - }, - handleHeader(file, remove = false) { - this.header = remove ? undefined : file; - }, - handleScreenshot(screenshots, index, remove = false, id = undefined) { - if (remove) { - if (id !== undefined) { - index = this.screenshotIds.indexOf(id); - } - - if (index < 0) return; - - this.screenshots.splice(index, 1); - this.screenshotIds.splice(index, 1); - //? force variable update as slice method does only internal stuff - Vue.set(this, "screenshots", this.screenshots); - Vue.set(this, "screenshotIds", this.screenshotIds); - - //? that will force computed screenSources to be recomputed - } else { - const files = Array.isArray(screenshots) ? screenshots : [screenshots]; - const number = files.length; - - // First add screenshots - this.screenshots = [...this.screenshots, ...files]; - // Then add the same amount of ids - this.screenshotIds = [ - ...this.screenshotIds, - ...Array.from({ length: number }).map((_, i) => this.screenshotId + i), - ]; - - this.screenshotId += number; // increase top id - } - }, - }, + data() { + return { + header: undefined, + screenshots: [], + screenshotIds: [], + screenshotId: 0, + }; + }, + computed: { + screenSources() { + return this.screenshots.map((file) => URL.createObjectURL(file)); + }, + }, + methods: { + handleSubmit(data) { + // 1. Upload + let id; + axios + .post(this.$root.apiURL + "/addons", data, this.$root.apiOptions) + .then(async (response) => { + const addon = response.data; + id = addon.id; + + const promises = []; + // 2. Upload header and screenshots + let form; + if (this.header || this.screenshots.length) { + form = new FormData(); + } + + if (this.header) { + form.set("file", this.header, this.header.name); + promises.push( + axios.post( + this.$root.apiURL + "/addons/" + id + "/header", + form, + this.$root.apiOptions, + ), + ); + } + if (this.screenshots.length) { + // add all of them + // fix to stabilize upload and make one request then another... + let i = 0; + let successful = true; + let err; + let screenshots = this.screenshots; + while (i < screenshots.length && successful) { + const screen = screenshots[i]; + const form = new FormData(); + form.set("file", screen, screen.name); + + successful = await axios + .post( + this.$root.apiURL + "/addons/" + id + "/screenshots", + form, + this.$root.apiOptions, + ) + .then(() => true) + .catch((error) => { + err = error; + return false; + }); + + i++; + } + + promises.push(successful ? Promise.resolve() : Promise.reject(err)); + } + + return Promise.all(promises); + }) + .then(() => { + this.$root.showSnackBar("Saved", "success"); + this.$router.push("/addons/submissions"); + }) + .catch((err) => { + console.error(err); + this.$root.showSnackBar(err, "error"); + + // delete what is left of addon + // if we have id then we at least successfully created the file + if (id) + axios + .delete(this.$root.apiURL + "/addons/" + id, this.$root.apiOptions) + .catch((err) => { + this.$root.showSnackBar(err, "error"); + }); + }); + }, + handleHeader(file, remove = false) { + this.header = remove ? undefined : file; + }, + handleScreenshot(screenshots, index, remove = false, id = undefined) { + if (remove) { + if (id !== undefined) { + index = this.screenshotIds.indexOf(id); + } + + if (index < 0) return; + + this.screenshots.splice(index, 1); + this.screenshotIds.splice(index, 1); + //? force variable update as slice method does only internal stuff + Vue.set(this, "screenshots", this.screenshots); + Vue.set(this, "screenshotIds", this.screenshotIds); + + //? that will force computed screenSources to be recomputed + } else { + const files = Array.isArray(screenshots) ? screenshots : [screenshots]; + const number = files.length; + + // First add screenshots + this.screenshots = [...this.screenshots, ...files]; + // Then add the same amount of ids + this.screenshotIds = [ + ...this.screenshotIds, + ...Array.from({ length: number }).map((_, i) => this.screenshotId + i), + ]; + + this.screenshotId += number; // increase top id + } + }, + }, }; diff --git a/pages/addon/remove-confirm.js b/pages/addon/remove-confirm.js index 0cb1694..322084a 100644 --- a/pages/addon/remove-confirm.js +++ b/pages/addon/remove-confirm.js @@ -1,8 +1,8 @@ /* global axios */ export default { - name: "addon-remove-confirm", - template: ` + name: "addon-remove-confirm", + template: ` `, - props: { - confirm: { - type: Boolean, - required: true, - }, - data: { - type: Object, - required: true, - }, - disableDialog: { - type: Function, - required: true, - }, - }, - computed: { - title() { - return this.$props.data.name; - }, - }, - methods: { - deleteAddon() { - const addon_id = JSON.parse(JSON.stringify(this.$props.data.id)); + props: { + confirm: { + type: Boolean, + required: true, + }, + data: { + type: Object, + required: true, + }, + disableDialog: { + type: Function, + required: true, + }, + }, + computed: { + title() { + return this.$props.data.name; + }, + }, + methods: { + deleteAddon() { + const addon_id = JSON.parse(JSON.stringify(this.$props.data.id)); - axios - .delete(this.$root.apiURL + "/addons/" + addon_id, this.$root.apiOptions) - .then(() => { - this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); - this.disableDialog(true); - }) - .catch((error) => { - console.error(error); - this.$root.showSnackBar(`${error.message} : ${error.response.data.error}`, "error"); - this.disableDialog(true); - }); - }, - }, + axios + .delete(this.$root.apiURL + "/addons/" + addon_id, this.$root.apiOptions) + .then(() => { + this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); + this.disableDialog(true); + }) + .catch((error) => { + console.error(error); + this.$root.showSnackBar(`${error.message} : ${error.response.data.error}`, "error"); + this.disableDialog(true); + }); + }, + }, }; diff --git a/pages/addon/submissions.js b/pages/addon/submissions.js index ac61ada..83e8994 100644 --- a/pages/addon/submissions.js +++ b/pages/addon/submissions.js @@ -3,11 +3,11 @@ const addonRemoveConfirm = () => import("./remove-confirm.js"); export default { - name: "own-addon-page", - components: { - addonRemoveConfirm, - }, - template: ` + name: "own-addon-page", + components: { + addonRemoveConfirm, + }, + template: `
{{ $root.lang().addons.titles.submissions }} @@ -99,48 +99,48 @@ export default { `, - data() { - return { - addons: [], - remove: { - confirm: false, - data: {}, - }, - dialogAddon: {}, - dialogOpen: false, - loading: true, - failed: {}, - timestamp: new Date().getTime(), - }; - }, - methods: { - closeDialog() { - this.dialogOpen = false; - this.dialogAddon = {}; - this.update(); - }, - deleteAddon(addon) { - this.remove.data = addon; - this.remove.confirm = true; - }, - getAddons(authorID) { - axios - .get(`${this.$root.apiURL}/users/${authorID}/addons`, this.$root.apiOptions) - .then((res) => { - this.addons = res.data; - this.loading = false; - this.$forceUpdate(); - }) - .catch(function (err) { - console.error(err); - }); - }, - update() { - this.getAddons(this.$root.user.id); - this.$forceUpdate(); - }, - }, - mounted() { - this.getAddons(this.$root.user.id); - }, + data() { + return { + addons: [], + remove: { + confirm: false, + data: {}, + }, + dialogAddon: {}, + dialogOpen: false, + loading: true, + failed: {}, + timestamp: new Date().getTime(), + }; + }, + methods: { + closeDialog() { + this.dialogOpen = false; + this.dialogAddon = {}; + this.update(); + }, + deleteAddon(addon) { + this.remove.data = addon; + this.remove.confirm = true; + }, + getAddons(authorID) { + axios + .get(`${this.$root.apiURL}/users/${authorID}/addons`, this.$root.apiOptions) + .then((res) => { + this.addons = res.data; + this.loading = false; + this.$forceUpdate(); + }) + .catch(function (err) { + console.error(err); + }); + }, + update() { + this.getAddons(this.$root.user.id); + this.$forceUpdate(); + }, + }, + mounted() { + this.getAddons(this.$root.user.id); + }, }; diff --git a/pages/addon/userlist.js b/pages/addon/userlist.js index 06867d8..8cee95e 100644 --- a/pages/addon/userlist.js +++ b/pages/addon/userlist.js @@ -1,21 +1,21 @@ /* global Vue, axios */ export default { - name: "user-list", - props: { - label: { - type: String, - required: true, - }, - hint: { - type: String, - required: true, - }, - value: { - required: true, - }, - }, - template: ` + name: "user-list", + props: { + label: { + type: String, + required: true, + }, + hint: { + type: String, + required: true, + }, + value: { + required: true, + }, + }, + template: ` `, - data() { - return { - val: [], - users: [], - }; - }, - methods: { - remove(id) { - const index = this.val.indexOf(id); - if (index >= 0) this.val.splice(index, 1); - }, - getUsersIDs() { - axios - .get(`${this.$root.apiURL}/users/names`) - .then((res) => { - this.users = res.data.sort((a, b) => { - if (!a.username && !b.username) return 0; - if (!a.username && b.username) return 1; - if (a.username && !b.username) return -1; + data() { + return { + val: [], + users: [], + }; + }, + methods: { + remove(id) { + const index = this.val.indexOf(id); + if (index >= 0) this.val.splice(index, 1); + }, + getUsersIDs() { + axios + .get(`${this.$root.apiURL}/users/names`) + .then((res) => { + this.users = res.data.sort((a, b) => { + if (!a.username && !b.username) return 0; + if (!a.username && b.username) return 1; + if (a.username && !b.username) return -1; - return a.username > b.username ? 1 : b.username > a.username ? -1 : 0; - }); - }) - .catch((err) => { - console.trace(err); - }); - }, - }, - watch: { - val(n) { - this.$emit("input", n); - }, - value: { - handler(n) { - this.val = n; - }, - immediate: true, - }, - }, - mounted() { - this.getUsersIDs(); - }, + return a.username > b.username ? 1 : b.username > a.username ? -1 : 0; + }); + }) + .catch((err) => { + console.trace(err); + }); + }, + }, + watch: { + val(n) { + this.$emit("input", n); + }, + value: { + handler(n) { + this.val = n; + }, + immediate: true, + }, + }, + mounted() { + this.getUsersIDs(); + }, }; diff --git a/pages/components/drop-zone.js b/pages/components/drop-zone.js index 928adc8..c7aec21 100644 --- a/pages/components/drop-zone.js +++ b/pages/components/drop-zone.js @@ -1,28 +1,28 @@ /* global Vue, axios */ export default { - name: "drop-zone", - props: { - accept: { - required: false, - type: String, - default: () => "image/jpg, image/jpeg, image/png, image/gif", - }, - multiple: { - required: false, - type: Boolean, - default: () => false, - }, - value: { - required: true, - }, - disabled: { - required: false, - type: Boolean, - default: () => false, - }, - }, - template: ` + name: "drop-zone", + props: { + accept: { + required: false, + type: String, + default: () => "image/jpg, image/jpeg, image/png, image/gif", + }, + multiple: { + required: false, + type: Boolean, + default: () => false, + }, + value: { + required: true, + }, + disabled: { + required: false, + type: Boolean, + default: () => false, + }, + }, + template: `
`, - data() { - return { - isDragging: false, - }; - }, - methods: { - onChange() { - const files = this.multiple ? [...this.$refs.file.files] : this.$refs.file.files[0]; - this.$emit("change", files); - this.$emit("input", files); - }, + data() { + return { + isDragging: false, + }; + }, + methods: { + onChange() { + const files = this.multiple ? [...this.$refs.file.files] : this.$refs.file.files[0]; + this.$emit("change", files); + this.$emit("input", files); + }, - dragover(e) { - if (this.disabled) return; + dragover(e) { + if (this.disabled) return; - e.preventDefault(); - this.isDragging = true; - }, + e.preventDefault(); + this.isDragging = true; + }, - dragleave(e) { - if (this.disabled) return; + dragleave(e) { + if (this.disabled) return; - e.preventDefault(); - this.isDragging = false; - }, + e.preventDefault(); + this.isDragging = false; + }, - drop(e) { - if (this.disabled) return; + drop(e) { + if (this.disabled) return; - e.preventDefault(); - this.$refs.file.files = e.dataTransfer.files; - this.onChange(); - this.isDragging = false; - }, + e.preventDefault(); + this.$refs.file.files = e.dataTransfer.files; + this.onChange(); + this.isDragging = false; + }, - click() { - if (this.disabled) return; - this.$refs.file.click(); - this.$refs.file.blur(); - }, - }, + click() { + if (this.disabled) return; + this.$refs.file.click(); + this.$refs.file.blur(); + }, + }, }; diff --git a/pages/components/multi-range-input.js b/pages/components/multi-range-input.js index 3df34a5..4bafde1 100644 --- a/pages/components/multi-range-input.js +++ b/pages/components/multi-range-input.js @@ -1,23 +1,23 @@ /* global Vue, axios */ export default { - name: "multi-range-input", - props: { - value: { - required: true, - }, - labels: { - required: false, - default: () => ({ - one_required: "One value required", - incorrect_value: "%value% value incorrect", - }), - }, - multiple: { - type: Boolean, - required: true, - }, - }, - template: ` + name: "multi-range-input", + props: { + value: { + required: true, + }, + labels: { + required: false, + default: () => ({ + one_required: "One value required", + incorrect_value: "%value% value incorrect", + }), + }, + multiple: { + type: Boolean, + required: true, + }, + }, + template: ` `, - data() { - return { - ranges: [], - }; - }, - computed: { - valid_ranges() { - return this.ranges.filter((r) => this.checkRules([r]) === true); - }, - styled_ranges() { - return this.transformToStyled(this.valid_ranges); - }, - generated_values() { - return this.transformToGeneratedRange(this.styled_ranges); - }, - }, - methods: { - checkRules(ranges) { - let result = true; + data() { + return { + ranges: [], + }; + }, + computed: { + valid_ranges() { + return this.ranges.filter((r) => this.checkRules([r]) === true); + }, + styled_ranges() { + return this.transformToStyled(this.valid_ranges); + }, + generated_values() { + return this.transformToGeneratedRange(this.styled_ranges); + }, + }, + methods: { + checkRules(ranges) { + let result = true; - const rules = this.getRules(); + const rules = this.getRules(); - let i = 0; - while (result === true && i < rules.length) { - const ok = rules[i](ranges); - if (ok === false || typeof ok === "string") result = ok; - i += 1; - } + let i = 0; + while (result === true && i < rules.length) { + const ok = rules[i](ranges); + if (ok === false || typeof ok === "string") result = ok; + i += 1; + } - return result; - }, - getRules() { - return [ - (list) => !!list.length || this.labels.one_required, - (list) => { - let incorrectValue; - let i = 0; - while (incorrectValue === undefined && i < list.length) { - const s = list[i]; - const correct = (function () { - if (String(Number.parseInt(s, 10)) === s) return true; - const numbers = s.split(/\s*-\s*/); - if (numbers.length !== 2) return false; - return ( - String(Number.parseInt(numbers[0], 10)) === numbers[0] && - String(Number.parseInt(numbers[1], 10)) === numbers[1] - ); - })(); - if (!correct) incorrectValue = s; - i += 1; - } - return incorrectValue - ? this.labels.incorrect_value.replace("%value%", incorrectValue) - : true; - }, - ]; - }, - isNavCombo(keyboard_event) { - const { key } = keyboard_event; - const is_copy_paste = - (keyboard_event.ctrlKey && - (key == "c" || key == "x" || key == "v" || key == "a" || key == "u")) || - keyboard_event.keyCode == 123; - if (is_copy_paste) return true; - return key === "Backspace" || (key === "Delete" && key.includes("Arrow")); - }, - onInput(keyboard_event) { - const { key } = keyboard_event; - const not_a_num_key = Number.isNaN(Number.parseInt(key, 10)); - if (key !== " " && key !== "-" && not_a_num_key && !this.isNavCombo(keyboard_event)) { - keyboard_event.preventDefault(); - } - }, - transformToRaw(ranges) { - return ranges.map((r) => (Array.isArray(r) ? r.join(" - ") : String(r))); - }, - transformToStyled(ranges) { - return ranges.map((r) => r.split(/\s*-\s*/).map((v) => Number.parseInt(v))); - }, - transformToGeneratedRange(ranges) { - let res = []; - ranges.forEach((range) => { - if (range.length === 1) res.push(range[0]); - else { - const min = Math.min(range[1], range[0]); - res = [ - ...res, - ...Array.from(new Array(Math.abs(range[1] - range[0]) + 1).keys()).map((n) => n + min), - ]; - } - }); + return result; + }, + getRules() { + return [ + (list) => !!list.length || this.labels.one_required, + (list) => { + let incorrectValue; + let i = 0; + while (incorrectValue === undefined && i < list.length) { + const s = list[i]; + const correct = (function () { + if (String(Number.parseInt(s, 10)) === s) return true; + const numbers = s.split(/\s*-\s*/); + if (numbers.length !== 2) return false; + return ( + String(Number.parseInt(numbers[0], 10)) === numbers[0] && + String(Number.parseInt(numbers[1], 10)) === numbers[1] + ); + })(); + if (!correct) incorrectValue = s; + i += 1; + } + return incorrectValue + ? this.labels.incorrect_value.replace("%value%", incorrectValue) + : true; + }, + ]; + }, + isNavCombo(keyboard_event) { + const { key } = keyboard_event; + const is_copy_paste = + (keyboard_event.ctrlKey && + (key == "c" || key == "x" || key == "v" || key == "a" || key == "u")) || + keyboard_event.keyCode == 123; + if (is_copy_paste) return true; + return key === "Backspace" || (key === "Delete" && key.includes("Arrow")); + }, + onInput(keyboard_event) { + const { key } = keyboard_event; + const not_a_num_key = Number.isNaN(Number.parseInt(key, 10)); + if (key !== " " && key !== "-" && not_a_num_key && !this.isNavCombo(keyboard_event)) { + keyboard_event.preventDefault(); + } + }, + transformToRaw(ranges) { + return ranges.map((r) => (Array.isArray(r) ? r.join(" - ") : String(r))); + }, + transformToStyled(ranges) { + return ranges.map((r) => r.split(/\s*-\s*/).map((v) => Number.parseInt(v))); + }, + transformToGeneratedRange(ranges) { + let res = []; + ranges.forEach((range) => { + if (range.length === 1) res.push(range[0]); + else { + const min = Math.min(range[1], range[0]); + res = [ + ...res, + ...Array.from(new Array(Math.abs(range[1] - range[0]) + 1).keys()).map((n) => n + min), + ]; + } + }); - const no_duplicates = res.filter((e, i, a) => a.indexOf(e) === i); - no_duplicates.sort((a, b) => (a === b ? 0 : a < b ? -1 : 1)); - return no_duplicates; - }, - }, - mounted() { - this.$refs.form.validate(); - }, - created() { - window.generateRange = this.transformToGeneratedRange; - }, - watch: { - value: { - handler(n, o) { - if (n === undefined || JSON.stringify(n) === JSON.stringify(o)) return; + const no_duplicates = res.filter((e, i, a) => a.indexOf(e) === i); + no_duplicates.sort((a, b) => (a === b ? 0 : a < b ? -1 : 1)); + return no_duplicates; + }, + }, + mounted() { + this.$refs.form.validate(); + }, + created() { + window.generateRange = this.transformToGeneratedRange; + }, + watch: { + value: { + handler(n, o) { + if (n === undefined || JSON.stringify(n) === JSON.stringify(o)) return; - const transformed_value = this.multiple ? this.transformToRaw(n) : [String(n)]; + const transformed_value = this.multiple ? this.transformToRaw(n) : [String(n)]; - if (JSON.stringify(this.ranges) === JSON.stringify(transformed_value)) return; + if (JSON.stringify(this.ranges) === JSON.stringify(transformed_value)) return; - this.ranges = transformed_value; - }, - immediate: true, - deep: true, - }, - styled_ranges: { - handler(n, o) { - const sent = this.multiple ? n : n.flat()[0]; - this.$emit("input", sent); - }, - immediate: true, - deep: true, - }, - valid_ranges: { - handler(n) { - this.$emit("valid-ranges", n); - }, - immediate: true, - deep: true, - }, - generated_values: { - handler(n) { - this.$emit("generated-values", n); - }, - immediate: true, - deep: true, - }, - }, + this.ranges = transformed_value; + }, + immediate: true, + deep: true, + }, + styled_ranges: { + handler(n, o) { + const sent = this.multiple ? n : n.flat()[0]; + this.$emit("input", sent); + }, + immediate: true, + deep: true, + }, + valid_ranges: { + handler(n) { + this.$emit("valid-ranges", n); + }, + immediate: true, + deep: true, + }, + generated_values: { + handler(n) { + this.$emit("generated-values", n); + }, + immediate: true, + deep: true, + }, + }, }; diff --git a/pages/components/quick-date-picker.js b/pages/components/quick-date-picker.js index 2830deb..648e4a6 100644 --- a/pages/components/quick-date-picker.js +++ b/pages/components/quick-date-picker.js @@ -1,34 +1,34 @@ /* global Vue, axios */ export default { - name: "quick-date-picker", - props: { - months: { - required: true, - }, - value: { - required: true, - }, - disabled: { - type: Boolean, - required: false, - default: () => false, - }, - flat: { - type: Boolean, - required: false, - default: () => false, - }, - labels: { - required: false, - default: () => ({ year: "Year", month: "Month", day: "Day" }), - }, - block: { - type: Boolean, - required: false, - default: () => false, - }, - }, - template: ` + name: "quick-date-picker", + props: { + months: { + required: true, + }, + value: { + required: true, + }, + disabled: { + type: Boolean, + required: false, + default: () => false, + }, + flat: { + type: Boolean, + required: false, + default: () => false, + }, + labels: { + required: false, + default: () => ({ year: "Year", month: "Month", day: "Day" }), + }, + block: { + type: Boolean, + required: false, + default: () => false, + }, + }, + template: ` `, - data() { - return { - date: new Date(new Date(this.value).setHours(0, 0, 0, 0)), - }; - }, - computed: { - style() { - return this.block ? "width: 100%" : "width: 290px; max-width: 100%"; - }, - day() { - return this.date.getDate(); - }, - month() { - return this.date.getMonth(); - }, - year() { - return this.date.getFullYear(); - }, - date_str() { - return this.date.toDateString(); - }, - this_year: () => new Date().getFullYear(), - days_in_month() { - return this.daysInMonth(this.year, this.month + 1); - }, - upper_months() { - return this.months.map((name) => name[0].toUpperCase() + name.slice(1)); - }, - }, - methods: { - checkAndMaxDate(year, month) { - const newDate = new Date(year, month - 1, this.day); - if (newDate.getDate() != this.day) { - const days_in_new_month = this.daysInMonth(year, month); - const new_day = Math.min(this.day, days_in_new_month); - const corrected_date = new Date(new Date(year, month - 1, new_day).setHours(0, 0, 0, 0)); - this.date = corrected_date; - } - }, - newDay(i) { - this.date.setDate(i); - this.date = new Date(this.date); - }, - newMonth(m) { - this.checkAndMaxDate(this.year, m); - this.date.setMonth(m - 1); - this.date = new Date(this.date); - }, - newYear(e) { - let parsed = Number.parseInt(e); - let new_year = this.year; - if (!Number.isNaN(parsed)) new_year = parsed; + data() { + return { + date: new Date(new Date(this.value).setHours(0, 0, 0, 0)), + }; + }, + computed: { + style() { + return this.block ? "width: 100%" : "width: 290px; max-width: 100%"; + }, + day() { + return this.date.getDate(); + }, + month() { + return this.date.getMonth(); + }, + year() { + return this.date.getFullYear(); + }, + date_str() { + return this.date.toDateString(); + }, + this_year: () => new Date().getFullYear(), + days_in_month() { + return this.daysInMonth(this.year, this.month + 1); + }, + upper_months() { + return this.months.map((name) => name[0].toUpperCase() + name.slice(1)); + }, + }, + methods: { + checkAndMaxDate(year, month) { + const newDate = new Date(year, month - 1, this.day); + if (newDate.getDate() != this.day) { + const days_in_new_month = this.daysInMonth(year, month); + const new_day = Math.min(this.day, days_in_new_month); + const corrected_date = new Date(new Date(year, month - 1, new_day).setHours(0, 0, 0, 0)); + this.date = corrected_date; + } + }, + newDay(i) { + this.date.setDate(i); + this.date = new Date(this.date); + }, + newMonth(m) { + this.checkAndMaxDate(this.year, m); + this.date.setMonth(m - 1); + this.date = new Date(this.date); + }, + newYear(e) { + let parsed = Number.parseInt(e); + let new_year = this.year; + if (!Number.isNaN(parsed)) new_year = parsed; - this.checkAndMaxDate(new_year, this.month + 1); - this.date.setFullYear(new_year); - this.date = new Date(this.date); - }, - daysInMonth(year, month_i) { - return new Date(year, month_i, 0).getDate(); - }, - }, - watch: { - value: { - handler(n, o) { - if (new Date(n).getTime() !== new Date(o).getTime()) this.date = new Date(n); - }, - immediate: true, - deep: true, - }, - date() { - this.$emit("input", this.date.getTime()); - }, - }, + this.checkAndMaxDate(new_year, this.month + 1); + this.date.setFullYear(new_year); + this.date = new Date(this.date); + }, + daysInMonth(year, month_i) { + return new Date(year, month_i, 0).getDate(); + }, + }, + watch: { + value: { + handler(n, o) { + if (new Date(n).getTime() !== new Date(o).getTime()) this.date = new Date(n); + }, + immediate: true, + deep: true, + }, + date() { + this.$emit("input", this.date.getTime()); + }, + }, }; diff --git a/pages/contribution-stats/main.js b/pages/contribution-stats/main.js index 307cc3b..1779c3d 100644 --- a/pages/contribution-stats/main.js +++ b/pages/contribution-stats/main.js @@ -1,8 +1,8 @@ /* global axios, d3, moment */ export default { - name: "contributor-page", - components: {}, - template: ` + name: "contributor-page", + components: {}, + template: `
{{ $root.lang().statistics.title }} @@ -31,291 +31,291 @@ export default { `, - data() { - return { - texturesCount: 0, - authorsCount: 0, - contributionsCount: 0, - contrib: [], - }; - }, - methods: { - getData() { - axios - .get(`${this.$root.apiURL}/contributions/raw`) - .then((res) => Object.values(res.data)) - .then((data) => { - this.contributionsCount = data.length; - - const authors = data.map((el) => el.authors).flat(); - const textures = data.map((el) => el.texture); - const packs = data.map((el) => el.pack); - - this.authorsCount = authors.filter((el, index) => authors.indexOf(el) === index).length; - this.texturesCount = textures.filter( - (el, index) => textures.indexOf(el) === index, - ).length; - - this.contrib = Object.values( - data.reduce((ac, val) => { - val.date = moment(new Date(val.date)).startOf("day").unix() * 1000; - - if (!(val.date in ac)) { - // start for date - ac[val.date] = { - date: val.date, - pack: {}, - }; - - // start all res to 0 - packs.forEach((p) => { - ac[val.date].pack[p] = 0; - }); - } - - ac[val.date].pack[val.pack]++; - - return ac; - }, {}), - ); - - this.contrib.sort((a, b) => a.date - b.date); - this.buildGraph(); - }) - .catch(function (error) { - this.$root.showSnackBar(err, "error"); - }); - }, - - buildGraph() { - const width = 800; - const height = 500; - const spacing = 80; - const legendCellSize = 20; - - let data = this.contrib; - - // get all resolutions in provided - const allPack = data.length === 0 ? [] : Object.keys(data[0].pack).reverse(); - - // flat data (because stacked data is dumb) - data = this.contrib.map((el) => { - allPack.forEach((p) => { - el[p] = el.pack[p]; - }); - delete el.pack; - el.date = new Date(el.date); - return el; - }); - - this.changeDates(data); - - // sort by date - data.sort((a, b) => a.date - b.date); - - // first data are fucked - data = data.slice(4); - - // January first of 2021 is the Firestorm import with unknown dates to filter it too - data = data.filter((d, index) => index !== 4 && d.date !== new Date("2021-01-01").getTime()); - - const xScale = d3 - .scaleBand() - .domain(data.map((d) => d.date)) - .range([0, width - spacing]) - .padding(0.1); - - const yScale = d3.scaleLinear().range([height - spacing, 0]); - - // add center content inside svg - const svg = d3 - .select(document.getElementById("graph")) - .append("svg") - .attr("width", width) - .attr("height", height) - .append("g") - .attr("transform", "translate(" + spacing + "," + spacing / 4 + ")"); - - // create default time format - const timeFormat = d3.timeFormat("%b %d %Y"); - - // add bottom axis - const domain = xScale.domain(); - const totalTicks = 8; - const diffVided = Math.round(domain.length / totalTicks); - const values = domain.filter((el, index) => { - return index === 0 || index === domain.length - 1 || index % diffVided === 0; - }); - values.pop(); - const xAxis = d3.axisBottom(xScale).tickValues(values).tickFormat(timeFormat); - - svg - .append("g") - .attr("transform", "translate(0, " + (height - spacing) + ")") - .call(xAxis) - .selectAll("text") - .style("text-anchor", "end") - .attr("dx", "-.15em") - .attr("dy", ".15em") - .attr("transform", function () { - return "rotate(-45)"; - }); - - // create stack data from - const stack = d3.stack().keys(allPack).order(d3.stackOrderNone).offset(d3.stackOffsetNone); - - const series = stack(data); - const [max, secondMax] = data - .map((d) => d.faithful_32x + d.faithful_64x) - .reduce( - (acc, cur) => { - if (cur > acc[0]) { - acc[1] = acc[0]; - acc[0] = cur; - } else if (cur > acc[1]) { - acc[1] = cur; - } - return acc; - }, - [0, 0], - ); - - // scale y - if (series.length) yScale.domain([0, secondMax]); - - // add left axis - svg.append("g").call(d3.axisLeft(yScale)); - - const colors = ["#cccccc", "#333333"]; - const groups = svg - .selectAll("g.series") - .data(series) - .enter() - .append("g") - .attr("class", "series") - .style("fill", (d, i) => colors[i % colors.length]); - - const div = d3.select("body").append("div").attr("class", "tooltip").style("opacity", 0); - - groups - .selectAll("rect") - .data((d) => d) - .enter() - .append("rect") - .attr("x", (d) => xScale(d.data.date)) - .attr("width", xScale.bandwidth()) - .attr("y", (d) => yScale(d[1])) - .attr("height", (d) => height - spacing - yScale(d[1] - d[0])) - .on("mouseover", function (...args) { - const [event, d] = args; - div.transition().duration(200).style("opacity", 0.9); - div - .html( - timeFormat(d.data.date) + - "
" + - reverseKeysTitle[reverseKeys[d[0] === 0 ? 0 : 1]] + - " contributions
" + - (d[1] - d[0]) + - "", - ) - .style("left", event.pageX + "px") - .style("top", event.pageY + "px"); - }) - .on("mousemove", function (event) { - div.style("left", event.pageX + "px").style("top", event.pageY + "px"); - }) - .on("mouseout", function () { - div.transition().duration(500).style("opacity", 0); - }); - - const reverseColors = colors.reverse(); // To show categories in the same order as they are used - const reverseKeys = allPack.reverse(); - - const reverseKeysTitle = { - faithful_32x: "faithful_32x", - faithful_64x: "faithful_64x", - }; - - const legend = svg - .append("g") - .attr("transform-origin", "top right") - .attr("transform", "translate(" + (width - 150) + ", 20)"); - - // For each color, we add a square always at the same position on the X axis - // and moved depending on the square size & the index of color used in the Y axis - legend - .selectAll() - .data(reverseColors) - .enter() - .append("rect") - .attr("height", legendCellSize + "px") - .attr("width", legendCellSize + "px") - .attr("x", -25) - .attr("y", (d, i) => i * legendCellSize) - .style("fill", (d) => d); - - // We do the same with labels but we add 10px of margin - legend - .selectAll() - .data(reverseKeys.map((e) => reverseKeysTitle[e])) - .enter() - .append("text") - .attr( - "transform", - (d, i) => - "translate(" + - (legendCellSize - 20) + - ", " + - (i * legendCellSize + (legendCellSize - 13) / 2) + - ")", - ) - .attr("dy", legendCellSize / 1.6) // To center the text vertically to squares - .style("font-size", "13px") - .style("fill", "grey") - .text((d) => d); - }, - - changeDates(data) { - const dayArray = []; - - const lastDay = moment("2021-03-30"); - const day = moment("2020-08-01"); - - const secondsPerMinute = 60; - const secondsPerHour = 60 * secondsPerMinute; - const secondsPerDay = 24 * secondsPerHour; - const secondsPerMonth = 30.146 * secondsPerDay; - const secondsPerYear = 365 * secondsPerDay; - - while (day.diff(lastDay) !== 0) { - const stringDay = day.format("YYYY-DD-MM"); - const badDaySplit = stringDay.split("-").map((el) => parseInt(el)); - const time = - (badDaySplit[0] - 1970) * secondsPerYear + - (badDaySplit[1] - 1) * secondsPerMonth + - badDaySplit[2] * secondsPerDay; - dayArray.push({ - oriDate: new Date(day.unix() * 1000), - ori: day.unix(), - final: moment(stringDay).unix() || time, - }); - day.add(1, "day"); - } - - const now = Date.now(); - data.forEach((d) => { - if (d.date > now) { - const m = moment(d.date).startOf("day").unix(); - const matches = dayArray.filter((el) => Math.abs(el.final - m) < secondsPerMonth); - matches.sort((a, b) => Math.abs(a.final - m) - Math.abs(b.final - m)); - if (matches.length) { - d.date = matches[0].oriDate; - } - } - }); - }, - }, - created() { - this.getData(); - }, + data() { + return { + texturesCount: 0, + authorsCount: 0, + contributionsCount: 0, + contrib: [], + }; + }, + methods: { + getData() { + axios + .get(`${this.$root.apiURL}/contributions/raw`) + .then((res) => Object.values(res.data)) + .then((data) => { + this.contributionsCount = data.length; + + const authors = data.map((el) => el.authors).flat(); + const textures = data.map((el) => el.texture); + const packs = data.map((el) => el.pack); + + this.authorsCount = authors.filter((el, index) => authors.indexOf(el) === index).length; + this.texturesCount = textures.filter( + (el, index) => textures.indexOf(el) === index, + ).length; + + this.contrib = Object.values( + data.reduce((ac, val) => { + val.date = moment(new Date(val.date)).startOf("day").unix() * 1000; + + if (!(val.date in ac)) { + // start for date + ac[val.date] = { + date: val.date, + pack: {}, + }; + + // start all res to 0 + packs.forEach((p) => { + ac[val.date].pack[p] = 0; + }); + } + + ac[val.date].pack[val.pack]++; + + return ac; + }, {}), + ); + + this.contrib.sort((a, b) => a.date - b.date); + this.buildGraph(); + }) + .catch(function (error) { + this.$root.showSnackBar(err, "error"); + }); + }, + + buildGraph() { + const width = 800; + const height = 500; + const spacing = 80; + const legendCellSize = 20; + + let data = this.contrib; + + // get all resolutions in provided + const allPack = data.length === 0 ? [] : Object.keys(data[0].pack).reverse(); + + // flat data (because stacked data is dumb) + data = this.contrib.map((el) => { + allPack.forEach((p) => { + el[p] = el.pack[p]; + }); + delete el.pack; + el.date = new Date(el.date); + return el; + }); + + this.changeDates(data); + + // sort by date + data.sort((a, b) => a.date - b.date); + + // first data are fucked + data = data.slice(4); + + // January first of 2021 is the Firestorm import with unknown dates to filter it too + data = data.filter((d, index) => index !== 4 && d.date !== new Date("2021-01-01").getTime()); + + const xScale = d3 + .scaleBand() + .domain(data.map((d) => d.date)) + .range([0, width - spacing]) + .padding(0.1); + + const yScale = d3.scaleLinear().range([height - spacing, 0]); + + // add center content inside svg + const svg = d3 + .select(document.getElementById("graph")) + .append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", "translate(" + spacing + "," + spacing / 4 + ")"); + + // create default time format + const timeFormat = d3.timeFormat("%b %d %Y"); + + // add bottom axis + const domain = xScale.domain(); + const totalTicks = 8; + const diffVided = Math.round(domain.length / totalTicks); + const values = domain.filter((el, index) => { + return index === 0 || index === domain.length - 1 || index % diffVided === 0; + }); + values.pop(); + const xAxis = d3.axisBottom(xScale).tickValues(values).tickFormat(timeFormat); + + svg + .append("g") + .attr("transform", "translate(0, " + (height - spacing) + ")") + .call(xAxis) + .selectAll("text") + .style("text-anchor", "end") + .attr("dx", "-.15em") + .attr("dy", ".15em") + .attr("transform", function () { + return "rotate(-45)"; + }); + + // create stack data from + const stack = d3.stack().keys(allPack).order(d3.stackOrderNone).offset(d3.stackOffsetNone); + + const series = stack(data); + const [max, secondMax] = data + .map((d) => d.faithful_32x + d.faithful_64x) + .reduce( + (acc, cur) => { + if (cur > acc[0]) { + acc[1] = acc[0]; + acc[0] = cur; + } else if (cur > acc[1]) { + acc[1] = cur; + } + return acc; + }, + [0, 0], + ); + + // scale y + if (series.length) yScale.domain([0, secondMax]); + + // add left axis + svg.append("g").call(d3.axisLeft(yScale)); + + const colors = ["#cccccc", "#333333"]; + const groups = svg + .selectAll("g.series") + .data(series) + .enter() + .append("g") + .attr("class", "series") + .style("fill", (d, i) => colors[i % colors.length]); + + const div = d3.select("body").append("div").attr("class", "tooltip").style("opacity", 0); + + groups + .selectAll("rect") + .data((d) => d) + .enter() + .append("rect") + .attr("x", (d) => xScale(d.data.date)) + .attr("width", xScale.bandwidth()) + .attr("y", (d) => yScale(d[1])) + .attr("height", (d) => height - spacing - yScale(d[1] - d[0])) + .on("mouseover", function (...args) { + const [event, d] = args; + div.transition().duration(200).style("opacity", 0.9); + div + .html( + timeFormat(d.data.date) + + "
" + + reverseKeysTitle[reverseKeys[d[0] === 0 ? 0 : 1]] + + " contributions
" + + (d[1] - d[0]) + + "", + ) + .style("left", event.pageX + "px") + .style("top", event.pageY + "px"); + }) + .on("mousemove", function (event) { + div.style("left", event.pageX + "px").style("top", event.pageY + "px"); + }) + .on("mouseout", function () { + div.transition().duration(500).style("opacity", 0); + }); + + const reverseColors = colors.reverse(); // To show categories in the same order as they are used + const reverseKeys = allPack.reverse(); + + const reverseKeysTitle = { + faithful_32x: "faithful_32x", + faithful_64x: "faithful_64x", + }; + + const legend = svg + .append("g") + .attr("transform-origin", "top right") + .attr("transform", "translate(" + (width - 150) + ", 20)"); + + // For each color, we add a square always at the same position on the X axis + // and moved depending on the square size & the index of color used in the Y axis + legend + .selectAll() + .data(reverseColors) + .enter() + .append("rect") + .attr("height", legendCellSize + "px") + .attr("width", legendCellSize + "px") + .attr("x", -25) + .attr("y", (d, i) => i * legendCellSize) + .style("fill", (d) => d); + + // We do the same with labels but we add 10px of margin + legend + .selectAll() + .data(reverseKeys.map((e) => reverseKeysTitle[e])) + .enter() + .append("text") + .attr( + "transform", + (d, i) => + "translate(" + + (legendCellSize - 20) + + ", " + + (i * legendCellSize + (legendCellSize - 13) / 2) + + ")", + ) + .attr("dy", legendCellSize / 1.6) // To center the text vertically to squares + .style("font-size", "13px") + .style("fill", "grey") + .text((d) => d); + }, + + changeDates(data) { + const dayArray = []; + + const lastDay = moment("2021-03-30"); + const day = moment("2020-08-01"); + + const secondsPerMinute = 60; + const secondsPerHour = 60 * secondsPerMinute; + const secondsPerDay = 24 * secondsPerHour; + const secondsPerMonth = 30.146 * secondsPerDay; + const secondsPerYear = 365 * secondsPerDay; + + while (day.diff(lastDay) !== 0) { + const stringDay = day.format("YYYY-DD-MM"); + const badDaySplit = stringDay.split("-").map((el) => parseInt(el)); + const time = + (badDaySplit[0] - 1970) * secondsPerYear + + (badDaySplit[1] - 1) * secondsPerMonth + + badDaySplit[2] * secondsPerDay; + dayArray.push({ + oriDate: new Date(day.unix() * 1000), + ori: day.unix(), + final: moment(stringDay).unix() || time, + }); + day.add(1, "day"); + } + + const now = Date.now(); + data.forEach((d) => { + if (d.date > now) { + const m = moment(d.date).startOf("day").unix(); + const matches = dayArray.filter((el) => Math.abs(el.final - m) < secondsPerMonth); + matches.sort((a, b) => Math.abs(a.final - m) - Math.abs(b.final - m)); + if (matches.length) { + d.date = matches[0].oriDate; + } + } + }); + }, + }, + created() { + this.getData(); + }, }; diff --git a/pages/contribution/contributionForm.js b/pages/contribution/contributionForm.js index 76cc1c3..b90493a 100644 --- a/pages/contribution/contributionForm.js +++ b/pages/contribution/contributionForm.js @@ -3,31 +3,31 @@ const quickDatePicker = () => import("../components/quick-date-picker.js"); const multiRangeInput = () => import("../components/multi-range-input.js"); export default { - name: "contribution-form", - components: { - "user-select": userSelect, - "quick-date-picker": quickDatePicker, - "multi-range-input": multiRangeInput, - }, - props: { - contributors: { - required: true, - type: Array, - }, - value: { - required: true, - }, - disabled: { - type: Boolean, - required: false, - default: () => false, - }, - multiple: { - type: Boolean, - required: true, - }, - }, - template: ` + name: "contribution-form", + components: { + "user-select": userSelect, + "quick-date-picker": quickDatePicker, + "multi-range-input": multiRangeInput, + }, + props: { + contributors: { + required: true, + type: Array, + }, + value: { + required: true, + }, + disabled: { + type: Boolean, + required: false, + default: () => false, + }, + multiple: { + type: Boolean, + required: true, + }, + }, + template: ` @@ -79,25 +79,25 @@ export default { `, - data() { - return { - content: this.value, - months: moment.monthsShort(), - }; - }, - watch: { - value: { - handler(n, o) { - if (n !== undefined && JSON.stringify(n) !== JSON.stringify(o)) this.content = n; - }, - immediate: true, - deep: true, - }, - content: { - handler(n) { - this.$emit("input", n); - }, - deep: true, - }, - }, + data() { + return { + content: this.value, + months: moment.monthsShort(), + }; + }, + watch: { + value: { + handler(n, o) { + if (n !== undefined && JSON.stringify(n) !== JSON.stringify(o)) this.content = n; + }, + immediate: true, + deep: true, + }, + content: { + handler(n) { + this.$emit("input", n); + }, + deep: true, + }, + }, }; diff --git a/pages/contribution/contributionModal.js b/pages/contribution/contributionModal.js index 3cf791e..a3d3382 100644 --- a/pages/contribution/contributionModal.js +++ b/pages/contribution/contributionModal.js @@ -3,32 +3,32 @@ const contributionForm = () => import("./contributionForm.js"); export default { - name: "contribution-modal", - components: { - "contribution-form": contributionForm, - }, - props: { - contributors: { - required: true, - type: Array, - }, - onCancel: { - required: false, - type: Function, - default() {}, - }, - onSubmit: { - required: false, - type: Function, - default() {}, - }, - multiple: { - required: false, - type: Boolean, - default: false, - }, - }, - template: ` + name: "contribution-modal", + components: { + "contribution-form": contributionForm, + }, + props: { + contributors: { + required: true, + type: Array, + }, + onCancel: { + required: false, + type: Function, + default() {}, + }, + onSubmit: { + required: false, + type: Function, + default() {}, + }, + multiple: { + required: false, + type: Boolean, + default: false, + }, + }, + template: ` `, - data() { - return { - modalOpened: false, - closeOnSubmit: true, - formRecords: {}, - packsList: [], - lastFormId: 0, - openedFormId: undefined, - }; - }, - computed: { - activeForm() { - if (this.openedFormId === undefined) return undefined; + data() { + return { + modalOpened: false, + closeOnSubmit: true, + formRecords: {}, + packsList: [], + lastFormId: 0, + openedFormId: undefined, + }; + }, + computed: { + activeForm() { + if (this.openedFormId === undefined) return undefined; - const form_obj = this.formRecords[this.openedFormId]; - if (form_obj === undefined) return undefined; + const form_obj = this.formRecords[this.openedFormId]; + if (form_obj === undefined) return undefined; - const res = JSON.parse(JSON.stringify(form_obj)); - return res; - }, - formRecordsList() { - return Object.values(this.formRecords); - }, - formRecordsLength() { - return this.formRecordsList.length; - }, - panelLabels() { - return Object.entries(this.formRecords) - .map(([form_id, form]) => [ - form_id, - `${form.pack} | ${moment(new Date(form.date)).format("ll")}`, - ]) - .reduce((acc, [form_id, form_label]) => ({ ...acc, [form_id]: form_label }), {}); - }, - }, - methods: { - addNewForm() { - // create new form - let form; + const res = JSON.parse(JSON.stringify(form_obj)); + return res; + }, + formRecordsList() { + return Object.values(this.formRecords); + }, + formRecordsLength() { + return this.formRecordsList.length; + }, + panelLabels() { + return Object.entries(this.formRecords) + .map(([form_id, form]) => [ + form_id, + `${form.pack} | ${moment(new Date(form.date)).format("ll")}`, + ]) + .reduce((acc, [form_id, form_label]) => ({ ...acc, [form_id]: form_label }), {}); + }, + }, + methods: { + addNewForm() { + // create new form + let form; - // match last opened form - if (this.openedFormId !== undefined) { - // make a copy - const new_form_id = this.getNewFormId(); - form = JSON.parse(JSON.stringify(this.formRecords[this.openedFormId])); - form.formId = new_form_id; - } else { - form = this.defaultValue(this.packsList); - } + // match last opened form + if (this.openedFormId !== undefined) { + // make a copy + const new_form_id = this.getNewFormId(); + form = JSON.parse(JSON.stringify(this.formRecords[this.openedFormId])); + form.formId = new_form_id; + } else { + form = this.defaultValue(this.packsList); + } - // add form - let new_form_id = form.formId; - Vue.set(this.formRecords, new_form_id, form); + // add form + let new_form_id = form.formId; + Vue.set(this.formRecords, new_form_id, form); - // make the opened form our created form - this.openedFormId = new_form_id; - }, - open(input_data_obj = undefined, input_packs_list, close_on_submit = true) { - this.packsList = input_packs_list; - this.modalOpened = true; - this.openedFormId = undefined; + // make the opened form our created form + this.openedFormId = new_form_id; + }, + open(input_data_obj = undefined, input_packs_list, close_on_submit = true) { + this.packsList = input_packs_list; + this.modalOpened = true; + this.openedFormId = undefined; - let created_form_obj; - if (input_data_obj !== undefined) { - created_form_obj = Object.assign({}, this.defaultValue(input_packs_list), input_data_obj); - } else { - // get one empty form - created_form_obj = this.defaultValue(input_packs_list); - } + let created_form_obj; + if (input_data_obj !== undefined) { + created_form_obj = Object.assign({}, this.defaultValue(input_packs_list), input_data_obj); + } else { + // get one empty form + created_form_obj = this.defaultValue(input_packs_list); + } - Vue.set(this, "formRecords", { - [created_form_obj.formId]: created_form_obj, - }); - this.openedFormId = created_form_obj.formId; - this.closeOnSubmit = !!close_on_submit; - }, - contributorsFromIds(author_ids) { - if (!author_ids || author_ids.length === 0) { - return ""; - } + Vue.set(this, "formRecords", { + [created_form_obj.formId]: created_form_obj, + }); + this.openedFormId = created_form_obj.formId; + this.closeOnSubmit = !!close_on_submit; + }, + contributorsFromIds(author_ids) { + if (!author_ids || author_ids.length === 0) { + return ""; + } - const contributor_names = this.contributors - .filter((c) => author_ids.indexOf(c.id) !== -1) - .map((c) => c.username); + const contributor_names = this.contributors + .filter((c) => author_ids.indexOf(c.id) !== -1) + .map((c) => c.username); - const total = contributor_names.length; - const anonymous_total = contributor_names.filter((username) => !username).length; - const known_names = contributor_names.filter((username) => username); + const total = contributor_names.length; + const anonymous_total = contributor_names.filter((username) => !username).length; + const known_names = contributor_names.filter((username) => username); - if (anonymous_total > 0) { - const anonymous_str = - "" + anonymous_total + " " + this.$root.lang("database.labels.anonymous"); - known_names.splice(0, 0, anonymous_str); - } + if (anonymous_total > 0) { + const anonymous_str = + "" + anonymous_total + " " + this.$root.lang("database.labels.anonymous"); + known_names.splice(0, 0, anonymous_str); + } - const known_str = known_names.join(" | "); - const total_str = `[${total}]: `; - return total_str + known_str; - }, - changeOpenedForm(form_id) { - if (this.openedFormId === form_id) this.openedFormId = undefined; - else this.openedFormId = form_id; - }, - close() { - this.modalOpened = false; - }, - closeAndCancel() { - this.close(); - this.onCancel(); - }, - async closeOrAndSubmit() { - const result_data_list = Object.values(this.formRecords).map((f) => { - delete f.formId; - return f; - }); + const known_str = known_names.join(" | "); + const total_str = `[${total}]: `; + return total_str + known_str; + }, + changeOpenedForm(form_id) { + if (this.openedFormId === form_id) this.openedFormId = undefined; + else this.openedFormId = form_id; + }, + close() { + this.modalOpened = false; + }, + closeAndCancel() { + this.close(); + this.onCancel(); + }, + async closeOrAndSubmit() { + const result_data_list = Object.values(this.formRecords).map((f) => { + delete f.formId; + return f; + }); - const data_purified = JSON.parse( - JSON.stringify(this.multiple ? result_data_list : result_data_list[0]), - ); + const data_purified = JSON.parse( + JSON.stringify(this.multiple ? result_data_list : result_data_list[0]), + ); - const went_well = await this.onSubmit(data_purified); + const went_well = await this.onSubmit(data_purified); - if (!went_well) return; // do not close some data may be incorrect or one contribution failed to be sent + if (!went_well) return; // do not close some data may be incorrect or one contribution failed to be sent - if (this.closeOnSubmit) this.modalOpened = false; - }, - defaultValue(packs_list) { - return { - date: new Date(new Date().setHours(0, 0, 0, 0)), - packs: packs_list, - pack: packs_list ? packs_list[0] : null, - texture: this.multiple ? [] : 0, - authors: [], - formId: this.getNewFormId(), - }; - }, - getNewFormId() { - this.lastFormId++; - return String(this.lastFormId); - }, - onFormInput(form) { - // stop undefined object - if (typeof form !== "object") return; - // stop non-form objects - if (!("formId" in form)) return; + if (this.closeOnSubmit) this.modalOpened = false; + }, + defaultValue(packs_list) { + return { + date: new Date(new Date().setHours(0, 0, 0, 0)), + packs: packs_list, + pack: packs_list ? packs_list[0] : null, + texture: this.multiple ? [] : 0, + authors: [], + formId: this.getNewFormId(), + }; + }, + getNewFormId() { + this.lastFormId++; + return String(this.lastFormId); + }, + onFormInput(form) { + // stop undefined object + if (typeof form !== "object") return; + // stop non-form objects + if (!("formId" in form)) return; - form = JSON.parse(JSON.stringify(form)); + form = JSON.parse(JSON.stringify(form)); - // stop unexisting forms - const form_id = form.formId; - if (!this.formRecords[form_id]) return; + // stop unexisting forms + const form_id = form.formId; + if (!this.formRecords[form_id]) return; - // now affect - Vue.set(this.formRecords, form_id, form); - }, - removeForm(form_id) { - // do not continue if not found - if (!this.formRecords[form_id]) return; + // now affect + Vue.set(this.formRecords, form_id, form); + }, + removeForm(form_id) { + // do not continue if not found + if (!this.formRecords[form_id]) return; - const form_ids_list = Object.keys(this.formRecords); + const form_ids_list = Object.keys(this.formRecords); - // do not delete if only one - if (form_ids_list.length === 1) return; + // do not delete if only one + if (form_ids_list.length === 1) return; - // decide who will be the next form - const form_index = form_ids_list.indexOf(form_id); - const next_form_index = (form_index + 1) % form_ids_list.length; - const next_form_id = form_ids_list[next_form_index]; - this.openedFormId = next_form_id; + // decide who will be the next form + const form_index = form_ids_list.indexOf(form_id); + const next_form_index = (form_index + 1) % form_ids_list.length; + const next_form_id = form_ids_list[next_form_index]; + this.openedFormId = next_form_id; - const new_form_records = Object.assign({}, this.formRecords); // clean - delete new_form_records[form_id]; // delete - Vue.set(this, "formRecords", new_form_records); // affect - }, - }, + const new_form_records = Object.assign({}, this.formRecords); // clean + delete new_form_records[form_id]; // delete + Vue.set(this, "formRecords", new_form_records); // affect + }, + }, }; diff --git a/pages/contribution/main.js b/pages/contribution/main.js index df2f912..40c97f0 100644 --- a/pages/contribution/main.js +++ b/pages/contribution/main.js @@ -2,18 +2,17 @@ const contributionModal = () => import("./contributionModal.js"); export default { - components: { - contributionModal, - }, - name: "contribution-page", - template: ` + components: { + contributionModal, + }, + name: "contribution-page", + template: ` - -
+
{{ $root.lang().database.titles.contributions }}
@@ -24,7 +23,7 @@ export default { -

{{ $root.lang().database.subtitles.pack }}

+

{{ $root.lang().database.subtitles.resource_packs }}

-

{{ $root.lang().database.subtitles.search }}

+

{{ $root.lang().database.subtitles.search }}


{{ $root.lang().global.no_results }}

`, - data() { - const INCREMENT = 250; + data() { + const INCREMENT = 250; - return { - maxheight: 170, - form: { - packs: [], // [{key: 'all', selected: true }] - }, - all_packs: "all", - all_packs_display: "All", - contributors: [], - contributors_selected: [], - packToCode: {}, - search: { - searching: false, - search_results: [], - }, - textureSearch: "", - displayedResults: INCREMENT, - newSubmit: false, - }; - }, - computed: { - queryToIds() { - if (this.$route.query.ids) { - return this.$route.query.ids.split("-"); - } + return { + maxheight: 170, + form: { + packs: [], // [{key: 'all', selected: true }] + }, + all_packs: "all", + all_packs_display: "All", + contributors: [], + contributors_selected: [], + packToCode: {}, + search: { + searching: false, + search_results: [], + }, + textureSearch: "", + displayedResults: INCREMENT, + newSubmit: false, + }; + }, + computed: { + queryToIds() { + if (this.$route.query.ids) { + return this.$route.query.ids.split("-"); + } - // use the logged user as default selected contributor - return []; - }, - idsToQuery() { - return { - ids: this.contributors_selected.join("-"), - }; - }, - searchDisabled() { - const resSelected = this.form.packs.reduce((a, c) => a || c.selected, false) === false; - const invalidTextSearch = - this.textureSearch.length < 3 && Number.isNaN(Number.parseInt(this.textureSearch)); - const result = - this.search.searching || - resSelected || - (this.contributors_selected.length === 0 && invalidTextSearch); - return result; - }, - listColumns() { - let columns = 1; + // use the logged user as default selected contributor + return []; + }, + idsToQuery() { + return { + ids: this.contributors_selected.join("-"), + }; + }, + searchDisabled() { + const resSelected = this.form.packs.reduce((a, c) => a || c.selected, false) === false; + const invalidTextSearch = + this.textureSearch.length < 3 && Number.isNaN(Number.parseInt(this.textureSearch)); + const result = + this.search.searching || + resSelected || + (this.contributors_selected.length === 0 && invalidTextSearch); + return result; + }, + listColumns() { + let columns = 1; - if (this.$vuetify.breakpoint.mdAndUp && this.contributors.length >= 6) { - columns = 2; - if (this.$vuetify.breakpoint.lgAndUp && this.contributors.length >= 21) { - columns = 3; - } - } + if (this.$vuetify.breakpoint.mdAndUp && this.contributors.length >= 6) { + columns = 2; + if (this.$vuetify.breakpoint.lgAndUp && this.contributors.length >= 21) { + columns = 3; + } + } - return columns; - }, - multiple() { - return this.newSubmit; - }, - packsSelected() { - return this.form.packs.filter((entry) => entry.selected); - }, - packsToChoose() { - return this.form.packs - .filter((entry) => entry.key !== this.all_packs) - .map((entry) => entry.key); - }, - splittedResults() { - const res = []; - for (let col = 0; col < this.listColumns; ++col) { - res.push([]); - } + return columns; + }, + multiple() { + return this.newSubmit; + }, + packsSelected() { + return this.form.packs.filter((entry) => entry.selected); + }, + packsToChoose() { + return this.form.packs + .filter((entry) => entry.key !== this.all_packs) + .map((entry) => entry.key); + }, + splittedResults() { + const res = []; + for (let col = 0; col < this.listColumns; ++col) { + res.push([]); + } - let arrayIndex = 0; - this.search.search_results.forEach((contrib) => { - res[arrayIndex].push(contrib); - arrayIndex = (arrayIndex + 1) % this.listColumns; - }); + let arrayIndex = 0; + this.search.search_results.forEach((contrib) => { + res[arrayIndex].push(contrib); + arrayIndex = (arrayIndex + 1) % this.listColumns; + }); - return res; - }, - onModalSubmit() { - return this.newSubmit ? this.onNewSubmit : this.onChangeSubmit; - }, - }, - methods: { - momo(...args) { - return moment(...args); - }, - showMore() { - this.displayedResults += 100; - }, - getRes() { - axios.get(`${this.$root.apiURL}/packs/search?type=submission`).then((res) => { - Object.values(res.data).forEach((r) => { - this.addPack(r.id, r.name); - }); - }); - }, - getAuthors() { - axios - .get(`${this.$root.apiURL}/contributions/authors`) - .then((res) => { - // assign the result, but sorted by username - this.contributors = res.data.sort((a, b) => { - if (!a.username && !b.username) return 0; - if (a.username && !b.username) return 1; - if (!a.username && b.username) return -1; + return res; + }, + onModalSubmit() { + return this.newSubmit ? this.onNewSubmit : this.onChangeSubmit; + }, + }, + methods: { + momo(...args) { + return moment(...args); + }, + showMore() { + this.displayedResults += 100; + }, + getRes() { + axios.get(`${this.$root.apiURL}/packs/search?type=submission`).then((res) => { + Object.values(res.data).forEach((r) => { + this.addPack(r.id, r.name); + }); + }); + }, + getAuthors() { + axios + .get(`${this.$root.apiURL}/contributions/authors`) + .then((res) => { + // assign the result, but sorted by username + this.contributors = res.data.sort((a, b) => { + if (!a.username && !b.username) return 0; + if (a.username && !b.username) return 1; + if (!a.username && b.username) return -1; - return a.username.toLowerCase() > b.username.toLowerCase() - ? 1 - : b.username.toLowerCase() > a.username.toLowerCase() - ? -1 - : 0; - }); - }) - .catch(console.trace); - }, - remove(id) { - const index = this.contributors_selected.indexOf(id); - if (index >= 0) this.contributors_selected.splice(index, 1); - }, - addPack(name, value, selected = false) { - this.form.packs.push({ - key: name, - value: value, - selected, - }); - }, - openAdd() { - this.newSubmit = true; - Vue.nextTick(() => { - this.$refs.mod.open(undefined, this.packsToChoose, true); - }); - }, - startSearch() { - this.search.searching = true; - axios - .get( - `${this.$root.apiURL}/contributions/search + return a.username.toLowerCase() > b.username.toLowerCase() + ? 1 + : b.username.toLowerCase() > a.username.toLowerCase() + ? -1 + : 0; + }); + }) + .catch(console.trace); + }, + remove(id) { + const index = this.contributors_selected.indexOf(id); + if (index >= 0) this.contributors_selected.splice(index, 1); + }, + addPack(name, value, selected = false) { + this.form.packs.push({ + key: name, + value: value, + selected, + }); + }, + openAdd() { + this.newSubmit = true; + Vue.nextTick(() => { + this.$refs.mod.open(undefined, this.packsToChoose, true); + }); + }, + startSearch() { + this.search.searching = true; + axios + .get( + `${this.$root.apiURL}/contributions/search ?packs=${this.packsSelected.map((r) => r.key).join("-")} &users=${this.contributors_selected.join("-")} &search=${this.textureSearch}`, - ) - .then((res) => { - res.data.sort((a, b) => b.date - a.date); - this.search.search_results = res.data.map((c) => { - return { ...c, url: `${this.$root.apiURL}/textures/${c.texture}/url/${c.pack}/latest` }; - }); - }) + ) + .then((res) => { + res.data.sort((a, b) => b.date - a.date); + this.search.search_results = res.data.map((c) => { + return { ...c, url: `${this.$root.apiURL}/textures/${c.texture}/url/${c.pack}/latest` }; + }); + }) - // fetch contribution textures names - .then(() => this.search.search_results.map((c) => c.texture)) - .then((all_ids) => { - // split request in groups - return Promise.all( - all_ids - .reduce((acc, cur, index) => { - if (index % 30 === 0) { - acc.push([]); - } - acc[acc.length - 1].push(cur); - return acc; - }, []) - .map((ids) => { - // optimize array search by deleting double - return axios.get( - `${this.$root.apiURL}/textures/${ids - .filter((v, i, a) => a.indexOf(v) === i) - .join(",")}`, - ); - }), - ); - }) - .then((results) => { - const texturesFromIds = results.map((r) => r.data).flat(); // remerge results + // fetch contribution textures names + .then(() => this.search.search_results.map((c) => c.texture)) + .then((all_ids) => { + // split request in groups + return Promise.all( + all_ids + .reduce((acc, cur, index) => { + if (index % 30 === 0) { + acc.push([]); + } + acc[acc.length - 1].push(cur); + return acc; + }, []) + .map((ids) => { + // optimize array search by deleting double + return axios.get( + `${this.$root.apiURL}/textures/${ids + .filter((v, i, a) => a.indexOf(v) === i) + .join(",")}`, + ); + }), + ); + }) + .then((results) => { + const texturesFromIds = results.map((r) => r.data).flat(); // remerge results - this.search.search_results.forEach((contrib) => { - const found_texture = texturesFromIds.find((t) => t.id === contrib.texture); - contrib.name = found_texture ? found_texture.name : ""; // find texture with null string fallback (sometimes we get a 404) - }); - }) + this.search.search_results.forEach((contrib) => { + const found_texture = texturesFromIds.find((t) => t.id === contrib.texture); + contrib.name = found_texture ? found_texture.name : ""; // find texture with null string fallback (sometimes we get a 404) + }); + }) - .finally(() => (this.search.searching = false)) - .catch((err) => this.$root.showSnackBar(err, "error")); - }, - editContribution(contrib) { - this.newSubmit = false; - this.$refs.mod.open(contrib, this.packsToChoose, false); - }, - /** - * @typedef MultipleContribution - * @type {object} - * @property {string[]} authors Author id array - * @property {string[]?} packs Resource pack name array - * @property {string} pack Contribution resource pack - * @property {string} date Contribution date - * @property {Array} texture Texture range array - */ - /** - * @param {Array} entries Input entrues - * @returns {Promise} - */ - async onNewSubmit(entries) { - if (!Array.isArray(entries)) return; + .finally(() => (this.search.searching = false)) + .catch((err) => this.$root.showSnackBar(err, "error")); + }, + editContribution(contrib) { + this.newSubmit = false; + this.$refs.mod.open(contrib, this.packsToChoose, false); + }, + /** + * @typedef MultipleContribution + * @type {object} + * @property {string[]} authors Author id array + * @property {string[]?} packs Resource pack name array + * @property {string} pack Contribution resource pack + * @property {string} date Contribution date + * @property {Array} texture Texture range array + */ + /** + * @param {Array} entries Input entrues + * @returns {Promise} + */ + async onNewSubmit(entries) { + if (!Array.isArray(entries)) return; - // prepare final data - let final_contributions = []; - for (let entry of entries) { - const generated_range = window.generateRange(entry.texture); + // prepare final data + let final_contributions = []; + for (let entry of entries) { + const generated_range = window.generateRange(entry.texture); - if (generated_range.length === 0) { - this.$root - .jsonSnackBar(entry) - .showSnackBar(this.$root.lang("database.labels.id_field_errors.one_required"), "error"); - console.error(entry); - return false; - } + if (generated_range.length === 0) { + this.$root + .jsonSnackBar(entry) + .showSnackBar(this.$root.lang("database.labels.id_field_errors.one_required"), "error"); + console.error(entry); + return false; + } - if (entry.authors.length === 0) { - this.$root - .jsonSnackBar(entry) - .showSnackBar(this.$root.lang("database.subtitles.no_contributor_yet"), "error"); - console.error(entry); - return false; - } + if (entry.authors.length === 0) { + this.$root + .jsonSnackBar(entry) + .showSnackBar(this.$root.lang("database.subtitles.no_contributor_yet"), "error"); + console.error(entry); + return false; + } - for (let texture_id of generated_range) { - const new_contribution = { - date: new Date(entry.date).getTime(), - resolution: Number.parseInt(entry.pack.match(/\d+/)[0], 10), - pack: entry.pack, - authors: entry.authors, - texture: String(texture_id), - }; - final_contributions.push(new_contribution); - } - } + for (let texture_id of generated_range) { + const new_contribution = { + date: new Date(entry.date).getTime(), + resolution: Number.parseInt(entry.pack.match(/\d+/)[0], 10), + pack: entry.pack, + authors: entry.authors, + texture: String(texture_id), + }; + final_contributions.push(new_contribution); + } + } - let i = 0; - let went_well = true; - while (went_well && i < final_contributions.length) { - went_well = await axios - .post(`${this.$root.apiURL}/contributions`, final_contributions[i], this.$root.apiOptions) - .then((_created_contribution) => { - return true; - }) - .catch((err) => { - this.$root.showSnackBar(err, "error"); - console.error(final_contributions[i]); - return false; - }); + let i = 0; + let went_well = true; + while (went_well && i < final_contributions.length) { + went_well = await axios + .post(`${this.$root.apiURL}/contributions`, final_contributions[i], this.$root.apiOptions) + .then((_created_contribution) => { + return true; + }) + .catch((err) => { + this.$root.showSnackBar(err, "error"); + console.error(final_contributions[i]); + return false; + }); - i++; - } + i++; + } - if (went_well) { - this.$root.showSnackBar(this.$root.lang("global.ends_success"), "success"); - this.getAuthors(); - } + if (went_well) { + this.$root.showSnackBar(this.$root.lang("global.ends_success"), "success"); + this.getAuthors(); + } - return went_well; - }, - onPackChange(selected, key) { - if (key === this.all_packs) { - if (selected) { - // just checked all, uncheck others - // better to make all of them not selected instead of replacing data - // more stable if more data in entries - this.form.packs.forEach((entry) => { - if (entry.key === this.all_packs) return; + return went_well; + }, + onPackChange(selected, key) { + if (key === this.all_packs) { + if (selected) { + // just checked all, uncheck others + // better to make all of them not selected instead of replacing data + // more stable if more data in entries + this.form.packs.forEach((entry) => { + if (entry.key === this.all_packs) return; - entry.selected = false; - }); - } else { - this.onPackUnselected(key); - } - } else { - // other pack - if (selected) { - // uncheck all - const index_all = this.form.packs.findIndex((entry) => entry.key === this.all_packs); - this.form.packs[index_all].selected = false; - } else { - this.onPackUnselected(key); - } - } - }, - onPackUnselected(key) { - // ensure at least one selected - if (this.packsSelected.length === 0) { - const index_entry = this.form.packs.findIndex((entry) => entry.key === key); + entry.selected = false; + }); + } else { + this.onPackUnselected(key); + } + } else { + // other pack + if (selected) { + // uncheck all + const index_all = this.form.packs.findIndex((entry) => entry.key === this.all_packs); + this.form.packs[index_all].selected = false; + } else { + this.onPackUnselected(key); + } + } + }, + onPackUnselected(key) { + // ensure at least one selected + if (this.packsSelected.length === 0) { + const index_entry = this.form.packs.findIndex((entry) => entry.key === key); - // needs to be changed on next tick, cannot change same data on same cycle - this.$nextTick(() => { - Vue.set(this.form.packs[index_entry], "selected", true); - }); - } else { - // do nothing, at least one is selected - } - }, - onChangeSubmit(data) { - axios - .put( - `${this.$root.apiURL}/contributions/${data.id}`, - { - date: data.date, - resolution: data.resolution, - pack: data.pack, - authors: data.authors, - texture: String(data.texture), - }, - this.$root.apiOptions, - ) - .then(() => { - this.$refs.mod.close(); - this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); - this.startSearch(); - }) - .catch((err) => { - this.$root.showSnackBar(err, "error"); - }); - }, - deleteContribution(id) { - axios - .delete(`${this.$root.apiURL}/contributions/${id}`, this.$root.apiOptions) - .then(() => { - this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); - this.startSearch(); // actualize shown data - }) - .catch((err) => { - this.$root.showSnackBar(err, "error"); - }); - }, - }, - created() { - axios.get(`${this.$root.apiURL}/packs/raw`).then((res) => { - this.packToCode = Object.values(res.data).reduce( - (acc, cur) => ({ - ...acc, - [cur.id]: cur.name - .split(" ") - // Classic Faithful 32x Programmer Art -> CF32PA - .map((el) => (isNaN(Number(el[0])) ? el[0].toUpperCase() : el.match(/\d+/g)?.[0])) - .join(""), - }), - {}, - ); - }); - this.contributors_selected = this.queryToIds; - this.addPack(this.all_packs, this.all_packs_display, true); - window.eventBus.$on("newContributor", (l) => { - this.contributors = l; - }); - }, - mounted() { - this.getRes(); - this.getAuthors(); - }, - watch: { - contributors: { - handler(contributors) { - // FIX BUG WHERE USERS WITH NO CONTRIBUTIONS GET INCLUDED IN SEARCH - const contributors_id = contributors.map((c) => c.id); - this.contributors_selected = this.contributors_selected.filter((c) => - contributors_id.includes(c), - ); - }, - deep: true, - }, - }, + // needs to be changed on next tick, cannot change same data on same cycle + this.$nextTick(() => { + Vue.set(this.form.packs[index_entry], "selected", true); + }); + } else { + // do nothing, at least one is selected + } + }, + onChangeSubmit(data) { + axios + .put( + `${this.$root.apiURL}/contributions/${data.id}`, + { + date: data.date, + resolution: data.resolution, + pack: data.pack, + authors: data.authors, + texture: String(data.texture), + }, + this.$root.apiOptions, + ) + .then(() => { + this.$refs.mod.close(); + this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); + this.startSearch(); + }) + .catch((err) => { + this.$root.showSnackBar(err, "error"); + }); + }, + deleteContribution(id) { + axios + .delete(`${this.$root.apiURL}/contributions/${id}`, this.$root.apiOptions) + .then(() => { + this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); + this.startSearch(); // actualize shown data + }) + .catch((err) => { + this.$root.showSnackBar(err, "error"); + }); + }, + }, + created() { + axios.get(`${this.$root.apiURL}/packs/raw`).then((res) => { + this.packToCode = Object.values(res.data).reduce( + (acc, cur) => ({ + ...acc, + [cur.id]: cur.name + .split(" ") + // Classic Faithful 32x Programmer Art -> CF32PA + .map((el) => (isNaN(Number(el[0])) ? el[0].toUpperCase() : el.match(/\d+/g)?.[0])) + .join(""), + }), + {}, + ); + }); + this.contributors_selected = this.queryToIds; + this.addPack(this.all_packs, this.all_packs_display, true); + window.eventBus.$on("newContributor", (l) => { + this.contributors = l; + }); + }, + mounted() { + this.getRes(); + this.getAuthors(); + }, + watch: { + contributors: { + handler(contributors) { + // FIX BUG WHERE USERS WITH NO CONTRIBUTIONS GET INCLUDED IN SEARCH + const contributors_id = contributors.map((c) => c.id); + this.contributors_selected = this.contributors_selected.filter((c) => + contributors_id.includes(c), + ); + }, + deep: true, + }, + }, }; diff --git a/pages/contribution/userSelect.js b/pages/contribution/userSelect.js index 7f7128a..e2dde2f 100644 --- a/pages/contribution/userSelect.js +++ b/pages/contribution/userSelect.js @@ -1,27 +1,27 @@ const SEARCH_DELAY = 300; export default { - name: "user-select", - props: { - contributors: { - required: true, - type: Array, - }, - value: { - required: true, - }, - dense: { - type: Boolean, - required: false, - default: () => false, - }, - limit: { - type: Number, - required: false, - default: () => 0, - }, - }, - template: ` + name: "user-select", + props: { + contributors: { + required: true, + type: Array, + }, + value: { + required: true, + }, + dense: { + type: Boolean, + required: false, + default: () => false, + }, + limit: { + type: Number, + required: false, + default: () => 0, + }, + }, + template: ` `, - computed: { - contributorList() { - return [...this.contributors, ...Object.values(this.loadedContributors)]; - }, - }, - data() { - return { - content: this.value, - search: null, - isSearching: false, - searchTimeout: undefined, - previousSearches: [], - loadedContributors: {}, - }; - }, - watch: { - value: { - handler(n, o) { - if (JSON.stringify(n) !== JSON.stringify(o)) this.content = n; - }, - immediate: true, - deep: true, - }, - content: { - handler(n) { - this.$emit("input", n); - }, - deep: true, - }, - search(val) { - if (!val) return; + computed: { + contributorList() { + return [...this.contributors, ...Object.values(this.loadedContributors)]; + }, + }, + data() { + return { + content: this.value, + search: null, + isSearching: false, + searchTimeout: undefined, + previousSearches: [], + loadedContributors: {}, + }; + }, + watch: { + value: { + handler(n, o) { + if (JSON.stringify(n) !== JSON.stringify(o)) this.content = n; + }, + immediate: true, + deep: true, + }, + content: { + handler(n) { + this.$emit("input", n); + }, + deep: true, + }, + search(val) { + if (!val) return; - if (this.searchTimeout) { - clearTimeout(this.searchTimeout); - } + if (this.searchTimeout) { + clearTimeout(this.searchTimeout); + } - this.searchTimeout = setTimeout(() => { - this.searchTimeout = undefined; - this.startSearch(val); - }, SEARCH_DELAY); - }, - loadedContributors: { - handler(n) { - window.eventBus.$emit("newContributor", this.contributorList); - }, - deep: true, - }, - }, - methods: { - remove(id) { - const index = this.content.indexOf(id); - if (index >= 0) this.content.splice(index, 1); - }, - startSearch(val) { - val = val.trim(); + this.searchTimeout = setTimeout(() => { + this.searchTimeout = undefined; + this.startSearch(val); + }, SEARCH_DELAY); + }, + loadedContributors: { + handler(n) { + window.eventBus.$emit("newContributor", this.contributorList); + }, + deep: true, + }, + }, + methods: { + remove(id) { + const index = this.content.indexOf(id); + if (index >= 0) this.content.splice(index, 1); + }, + startSearch(val) { + val = val.trim(); - // limit search on client and server side - if (val.length < 3) return; + // limit search on client and server side + if (val.length < 3) return; - // make search only if not searched before - let alreadySearched = false; - let i = 0; - while (i < this.previousSearches.length && !alreadySearched) { - alreadySearched = this.previousSearches[i].includes(val); - ++i; - } - if (alreadySearched) return; + // make search only if not searched before + let alreadySearched = false; + let i = 0; + while (i < this.previousSearches.length && !alreadySearched) { + alreadySearched = this.previousSearches[i].includes(val); + ++i; + } + if (alreadySearched) return; - this.previousSearches.push(val); - this.isSearching = true; + this.previousSearches.push(val); + this.isSearching = true; - axios - .get( - // we can assume contribution editors are admin - `${this.$root.apiURL}/users/role/all/${val}`, - this.$root.apiOptions, - ) - .then((res) => { - const results = res.data; - results.forEach((result) => { - // in case some clever guy forgot its username or uuid or anything - Vue.set( - this.loadedContributors, - result.id, - Object.merge( - { - username: "", - uuid: "", - type: [], - media: [], - }, - result, - ), - ); - }); - }) - .catch((err) => { - this.$root.showSnackBar(err, "error"); - console.error(err); - }) - .finally(() => { - this.isSearching = false; - }); - }, - }, + axios + .get( + // we can assume contribution editors are admin + `${this.$root.apiURL}/users/role/all/${val}`, + this.$root.apiOptions, + ) + .then((res) => { + const results = res.data; + results.forEach((result) => { + // in case some clever guy forgot its username or uuid or anything + Vue.set( + this.loadedContributors, + result.id, + Object.merge( + { + username: "", + uuid: "", + type: [], + media: [], + }, + result, + ), + ); + }); + }) + .catch((err) => { + this.$root.showSnackBar(err, "error"); + console.error(err); + }) + .finally(() => { + this.isSearching = false; + }); + }, + }, }; diff --git a/pages/dashboard/addon-card.js b/pages/dashboard/addon-card.js index 1fbd5d2..236de94 100644 --- a/pages/dashboard/addon-card.js +++ b/pages/dashboard/addon-card.js @@ -1,18 +1,18 @@ const DashBoardCard = () => import("./dashcard.js"); export default { - name: "addon-card", - components: { - "dashboard-card": DashBoardCard, - }, - props: { - admin: { - required: true, - type: Boolean, - default: false, - }, - }, - template: ` + name: "addon-card", + components: { + "dashboard-card": DashBoardCard, + }, + props: { + admin: { + required: true, + type: Boolean, + default: false, + }, + }, + template: ` `, - data() { - return { - data: undefined, - status_color: { - approved: "success--text", - pending: "warning--text", - denied: "error--text", - archived: "grey--text", - }, - loading: true, - loading_for: 1, - request_admin: false, - }; - }, - computed: { - adminResults() { - return this.data && Object.keys(this.data).length > 2; - }, - statuses() { - return Object.keys(this.status_color); - }, - roles() { - return this.$root.user.roles.length; - }, - url() { - return "/addons/stats" + (this.admin ? "-admin" : ""); - }, - }, - methods: { - get() { - this.loading = true; - axios - .get(this.$root.apiURL + this.url, this.$root.apiOptions) - .then((res) => { - this.data = res.data; - }) - .finally(() => { - this.loading = false; - }); - }, - }, - created() { - this.get(); - }, - watch: { - roles(n, o) { - if (n != o && this.admin) { - this.loading = true; - this.loading_for = 4; - this.get(); - } - }, - }, + data() { + return { + data: undefined, + status_color: { + approved: "success--text", + pending: "warning--text", + denied: "error--text", + archived: "grey--text", + }, + loading: true, + loading_for: 1, + request_admin: false, + }; + }, + computed: { + adminResults() { + return this.data && Object.keys(this.data).length > 2; + }, + statuses() { + return Object.keys(this.status_color); + }, + roles() { + return this.$root.user.roles.length; + }, + url() { + return "/addons/stats" + (this.admin ? "-admin" : ""); + }, + }, + methods: { + get() { + this.loading = true; + axios + .get(this.$root.apiURL + this.url, this.$root.apiOptions) + .then((res) => { + this.data = res.data; + }) + .finally(() => { + this.loading = false; + }); + }, + }, + created() { + this.get(); + }, + watch: { + roles(n, o) { + if (n != o && this.admin) { + this.loading = true; + this.loading_for = 4; + this.get(); + } + }, + }, }; diff --git a/pages/dashboard/contribution-card.js b/pages/dashboard/contribution-card.js index ea0088b..bf6481a 100644 --- a/pages/dashboard/contribution-card.js +++ b/pages/dashboard/contribution-card.js @@ -1,26 +1,26 @@ const DashBoardCard = () => import("./dashcard.js"); export default { - name: "contribution-card", - components: { - "dashboard-card": DashBoardCard, - }, - props: { - admin: { - required: true, - type: Boolean, - default: false, - }, - colors: { - required: true, - type: Array, - }, - statsListener: { - required: true, - type: Function, - }, - }, - template: ` + name: "contribution-card", + components: { + "dashboard-card": DashBoardCard, + }, + props: { + admin: { + required: true, + type: Boolean, + default: false, + }, + colors: { + required: true, + type: Array, + }, + statsListener: { + required: true, + type: Function, + }, + }, + template: ` `, - data() { - return { - data: undefined, - }; - }, - computed: { - url() { - return "/contributions/stats"; - }, - totals() { - if (!this.data) return [, , ,]; - return Object.keys(this.data) - .filter((e) => e.includes("total")) - .map((e) => { - return { - name: e.replace("total_", ""), - value: this.data[e], - }; - }); - }, - today() { - return new Date(); - }, - locale() { - return { - months: moment.monthsShort().map((e) => e[0].toUpperCase() + e.slice(1)), - days: moment.weekdaysShort().map((e) => e[0].toUpperCase() + e.slice(1)), - ...this.$root.lang().dashboard.locale, - }; - }, - }, - methods: { - get() { - axios.get(this.$root.apiURL + this.url, this.$root.apiOptions).then((res) => { - this.data = res.data; - }); - }, - }, - created() { - this.get(); - }, - watch: { - totals(n, o) { - if (!o) return; // o is undefined - if (!o.length) return; // o is empty + data() { + return { + data: undefined, + }; + }, + computed: { + url() { + return "/contributions/stats"; + }, + totals() { + if (!this.data) return [, , ,]; + return Object.keys(this.data) + .filter((e) => e.includes("total")) + .map((e) => { + return { + name: e.replace("total_", ""), + value: this.data[e], + }; + }); + }, + today() { + return new Date(); + }, + locale() { + return { + months: moment.monthsShort().map((e) => e[0].toUpperCase() + e.slice(1)), + days: moment.weekdaysShort().map((e) => e[0].toUpperCase() + e.slice(1)), + ...this.$root.lang().dashboard.locale, + }; + }, + }, + methods: { + get() { + axios.get(this.$root.apiURL + this.url, this.$root.apiOptions).then((res) => { + this.data = res.data; + }); + }, + }, + created() { + this.get(); + }, + watch: { + totals(n, o) { + if (!o) return; // o is undefined + if (!o.length) return; // o is empty - // run - this.statsListener(n); - }, - }, + // run + this.statsListener(n); + }, + }, }; diff --git a/pages/dashboard/contribution-stats-card.js b/pages/dashboard/contribution-stats-card.js index 1aa370a..880f3e8 100644 --- a/pages/dashboard/contribution-stats-card.js +++ b/pages/dashboard/contribution-stats-card.js @@ -1,18 +1,18 @@ const DashBoardCard = () => import("./dashcard.js"); export default { - name: "contribution-stats-card", - components: { - "dashboard-card": DashBoardCard, - }, - props: { - admin: { - required: true, - type: Boolean, - default: false, - }, - }, - template: ` + name: "contribution-stats-card", + components: { + "dashboard-card": DashBoardCard, + }, + props: { + admin: { + required: true, + type: Boolean, + default: false, + }, + }, + template: ` `, - data() { - return { - data: undefined, - }; - }, - computed: { - totals() { - if (!this.data) return []; - return this.data; - }, - }, - methods: { - onTotals(data) { - this.data = data; - }, - }, + data() { + return { + data: undefined, + }; + }, + computed: { + totals() { + if (!this.data) return []; + return this.data; + }, + }, + methods: { + onTotals(data) { + this.data = data; + }, + }, }; diff --git a/pages/dashboard/dashboard.js b/pages/dashboard/dashboard.js index c32b05f..96015ec 100644 --- a/pages/dashboard/dashboard.js +++ b/pages/dashboard/dashboard.js @@ -6,16 +6,16 @@ const ContributionStatsCard = () => import("./contribution-stats-card.js"); const FaithfulCard = () => import("./faithful-card.js"); export default { - name: "dashboardPage", - components: { - "addon-card": AddonCard, - "profile-card": ProfileCard, - "user-card": UserCard, - "contribution-card": ContributionCard, - "contribution-stats-card": ContributionStatsCard, - "faithful-card": FaithfulCard, - }, - template: ` + name: "dashboardPage", + components: { + "addon-card": AddonCard, + "profile-card": ProfileCard, + "user-card": UserCard, + "contribution-card": ContributionCard, + "contribution-stats-card": ContributionStatsCard, + "faithful-card": FaithfulCard, + }, + template: `
{{ $root.user.username ? $root.lang('dashboard.welcome_user').replace('%USER%', $root.user.username) : $root.lang('dashboard.welcome') }} @@ -41,27 +41,27 @@ export default {
`, - computed: { - admin() { - // if not logged in - if (!this.$root.isUserLogged) return false; + computed: { + admin() { + // if not logged in + if (!this.$root.isUserLogged) return false; - // if user not loaded - if (!this.$root.user) return false; + // if user not loaded + if (!this.$root.user) return false; - // check roles - return ( - this.$root.user.roles.includes("Administrator") || - this.$root.user.roles.includes("Developer") - ); - }, - colors() { - // https://colordesigner.io/gradient-generator - if (this.$root.isDark) { - return ["#1e1e1e", "#303c27", "#425d30", "#537f38", "#65a33f", "#76c945"]; - } else { - return ["#f0f0f0", "#b5dd9e", "#a6d889", "#97d374", "#87ce5d", "#76c945"]; - } - }, - }, + // check roles + return ( + this.$root.user.roles.includes("Administrator") || + this.$root.user.roles.includes("Developer") + ); + }, + colors() { + // https://colordesigner.io/gradient-generator + if (this.$root.isDark) { + return ["#1e1e1e", "#303c27", "#425d30", "#537f38", "#65a33f", "#76c945"]; + } else { + return ["#f0f0f0", "#b5dd9e", "#a6d889", "#97d374", "#87ce5d", "#76c945"]; + } + }, + }, }; diff --git a/pages/dashboard/dashcard.js b/pages/dashboard/dashcard.js index 2838ab5..a48db25 100644 --- a/pages/dashboard/dashcard.js +++ b/pages/dashboard/dashcard.js @@ -1,23 +1,23 @@ export default { - name: "dashboard-card", - props: { - title: { - required: false, - type: String, - default: "", - }, - go_to: { - required: false, - type: String, - default: undefined, - }, - can_go_to: { - required: false, - type: Boolean, - default: false, - }, - }, - template: ` + name: "dashboard-card", + props: { + title: { + required: false, + type: String, + default: "", + }, + go_to: { + required: false, + type: String, + default: undefined, + }, + can_go_to: { + required: false, + type: Boolean, + default: false, + }, + }, + template: ` {{ title }} diff --git a/pages/dashboard/faithful-card.js b/pages/dashboard/faithful-card.js index d68e814..f4521a8 100644 --- a/pages/dashboard/faithful-card.js +++ b/pages/dashboard/faithful-card.js @@ -1,16 +1,16 @@ const DashBoardCard = () => import("./dashcard.js"); export default { - name: "faithful-card", - components: { - "dashboard-card": DashBoardCard, - }, - props: { - show: { - required: true, - }, - }, - template: ` + name: "faithful-card", + components: { + "dashboard-card": DashBoardCard, + }, + props: { + show: { + required: true, + }, + }, + template: ` import("./dashcard.js"); export default { - name: "profile-card", - components: { - "dashboard-card": DashBoardCard, - }, - props: { - show: { - required: true, - }, - }, - template: ` + name: "profile-card", + components: { + "dashboard-card": DashBoardCard, + }, + props: { + show: { + required: true, + }, + }, + template: ` `, - computed: { - user() { - return this.$root.user; - }, - }, + computed: { + user() { + return this.$root.user; + }, + }, }; diff --git a/pages/dashboard/roles-graph.js b/pages/dashboard/roles-graph.js index 48ec9fe..644af47 100644 --- a/pages/dashboard/roles-graph.js +++ b/pages/dashboard/roles-graph.js @@ -1,24 +1,24 @@ export default { - name: "roles-graph", - props: { - series: { - required: true, - type: Array, - }, - labels: { - required: true, - type: Array, - }, - loading: { - required: true, - type: Boolean, - }, - colors: { - required: true, - type: Array, - }, - }, - template: ` + name: "roles-graph", + props: { + series: { + required: true, + type: Array, + }, + labels: { + required: true, + type: Array, + }, + loading: { + required: true, + type: Boolean, + }, + colors: { + required: true, + type: Array, + }, + }, + template: ` @@ -63,91 +63,91 @@ export default { `, - data() { - return { - ratio: 5 / 3, - height: 314, - renderComponent: false, - }; - }, - computed: { - width() { - return this.height * this.ratio; - }, - theme() { - return this.$root.isDark ? "dark" : "classic"; - }, - types() { - return this.labels.reduce((acc, cur, i) => ({ ...acc, [cur]: this.series[i] }), {}); - }, - values() { - return this.series - .map((e, i) => { - return [this.labels[i], e]; - }) - .sort((a, b) => b[1] - a[1]) - .map((e, i) => [i, ...e]); - }, - shade() { - let start = this.GColor(this.colors[this.colors.length - 1]); - let end = this.GColor(this.colors[0]); + data() { + return { + ratio: 5 / 3, + height: 314, + renderComponent: false, + }; + }, + computed: { + width() { + return this.height * this.ratio; + }, + theme() { + return this.$root.isDark ? "dark" : "classic"; + }, + types() { + return this.labels.reduce((acc, cur, i) => ({ ...acc, [cur]: this.series[i] }), {}); + }, + values() { + return this.series + .map((e, i) => { + return [this.labels[i], e]; + }) + .sort((a, b) => b[1] - a[1]) + .map((e, i) => [i, ...e]); + }, + shade() { + let start = this.GColor(this.colors[this.colors.length - 1]); + let end = this.GColor(this.colors[0]); - return this.createColorRange(start, end, this.labels.length).map((c) => this.toRGB(c)); - }, - }, - methods: { - GColor(r, g, b) { - if (r !== undefined && g === undefined && b === undefined) { - g = Number.parseInt(r.slice(3, 5), 16); - b = Number.parseInt(r.slice(5, 7), 16); - r = Number.parseInt(r.slice(1, 3), 16); - } - r = typeof r === "undefined" ? 0 : r; - g = typeof g === "undefined" ? 0 : g; - b = typeof b === "undefined" ? 0 : b; - return { r: r, g: g, b: b }; - }, - toRGB(gcolor) { - return `rgb(${gcolor.r}, ${gcolor.g}, ${gcolor.b})`; - }, - forceRerender() { - // Remove my-component from the DOM - this.renderComponent = false; + return this.createColorRange(start, end, this.labels.length).map((c) => this.toRGB(c)); + }, + }, + methods: { + GColor(r, g, b) { + if (r !== undefined && g === undefined && b === undefined) { + g = Number.parseInt(r.slice(3, 5), 16); + b = Number.parseInt(r.slice(5, 7), 16); + r = Number.parseInt(r.slice(1, 3), 16); + } + r = typeof r === "undefined" ? 0 : r; + g = typeof g === "undefined" ? 0 : g; + b = typeof b === "undefined" ? 0 : b; + return { r: r, g: g, b: b }; + }, + toRGB(gcolor) { + return `rgb(${gcolor.r}, ${gcolor.g}, ${gcolor.b})`; + }, + forceRerender() { + // Remove my-component from the DOM + this.renderComponent = false; - this.$nextTick(() => { - // Add the component back in - this.renderComponent = true; - }); - }, - createColorRange(c1, c2, n = 255) { - var colorList = [], - tmpColor, - variance; - for (var i = 0; i < n; i++) { - tmpColor = this.GColor(); - // i E [[0;n[[ - // i * 255 E [[0; 255*n [[ - // i * 255 / n [[0 ; 255 [[ - variance = (i * 255) / n; - tmpColor.g = c1.g + (variance * (c2.g - c1.g)) / 255; - tmpColor.b = c1.b + (variance * (c2.b - c1.b)) / 255; - tmpColor.r = c1.r + (variance * (c2.r - c1.r)) / 255; - colorList.push(tmpColor); - } - return colorList; - }, - }, - created() { - if (this.series.length !== this.labels.length) { - throw new Error("Failed to parse series and labels"); - } - }, - watch: { - theme() { - this.forceRerender(); - }, - loading(n) { - if (n === false) this.renderComponent = true; - }, - }, + this.$nextTick(() => { + // Add the component back in + this.renderComponent = true; + }); + }, + createColorRange(c1, c2, n = 255) { + var colorList = [], + tmpColor, + variance; + for (var i = 0; i < n; i++) { + tmpColor = this.GColor(); + // i E [[0;n[[ + // i * 255 E [[0; 255*n [[ + // i * 255 / n [[0 ; 255 [[ + variance = (i * 255) / n; + tmpColor.g = c1.g + (variance * (c2.g - c1.g)) / 255; + tmpColor.b = c1.b + (variance * (c2.b - c1.b)) / 255; + tmpColor.r = c1.r + (variance * (c2.r - c1.r)) / 255; + colorList.push(tmpColor); + } + return colorList; + }, + }, + created() { + if (this.series.length !== this.labels.length) { + throw new Error("Failed to parse series and labels"); + } + }, + watch: { + theme() { + this.forceRerender(); + }, + loading(n) { + if (n === false) this.renderComponent = true; + }, + }, }; diff --git a/pages/dashboard/user-card.js b/pages/dashboard/user-card.js index f9f5dc0..c5f50c7 100644 --- a/pages/dashboard/user-card.js +++ b/pages/dashboard/user-card.js @@ -2,23 +2,23 @@ const DashBoardCard = () => import("./dashcard.js"); const RolesGraph = () => import("./roles-graph.js"); export default { - name: "user-card", - components: { - "dashboard-card": DashBoardCard, - "roles-graph": RolesGraph, - }, - props: { - admin: { - required: true, - type: Boolean, - default: false, - }, - colors: { - required: true, - type: Array, - }, - }, - template: ` + name: "user-card", + components: { + "dashboard-card": DashBoardCard, + "roles-graph": RolesGraph, + }, + props: { + admin: { + required: true, + type: Boolean, + default: false, + }, + colors: { + required: true, + type: Array, + }, + }, + template: ` `, - data() { - return { - data: undefined, - }; - }, - computed: { - total() { - if (this.data && this.data.total) return this.data.total; - return ""; - }, - chart() { - return this.$refs.chart; - }, - url() { - return "/users/stats"; - }, - series() { - return this.data - ? Object.values(this.data.total_per_roles) - : new Array(14).fill(undefined).map(() => 0); - }, - labels() { - return this.data - ? Object.keys(this.data.total_per_roles) - : new Array(14).fill(undefined).map(() => "??"); - }, - }, - methods: { - get() { - axios.get(this.$root.apiURL + this.url, this.$root.apiOptions).then((res) => { - this.data = res.data; - }); - }, - }, - created() { - this.get(); - }, + data() { + return { + data: undefined, + }; + }, + computed: { + total() { + if (this.data && this.data.total) return this.data.total; + return ""; + }, + chart() { + return this.$refs.chart; + }, + url() { + return "/users/stats"; + }, + series() { + return this.data + ? Object.values(this.data.total_per_roles) + : new Array(14).fill(undefined).map(() => 0); + }, + labels() { + return this.data + ? Object.keys(this.data.total_per_roles) + : new Array(14).fill(undefined).map(() => "??"); + }, + }, + methods: { + get() { + axios.get(this.$root.apiURL + this.url, this.$root.apiOptions).then((res) => { + this.data = res.data; + }); + }, + }, + created() { + this.get(); + }, }; diff --git a/pages/gallery/gallery.js b/pages/gallery/gallery.js index 558157d..f491f1e 100644 --- a/pages/gallery/gallery.js +++ b/pages/gallery/gallery.js @@ -4,13 +4,13 @@ const galleryModal = () => import("./modal.js"); const textureTooltip = () => import("./gallery_tooltip.js"); const Chain = function (val) { - return { - value: val, - chain(predicate) { - if (this.value !== undefined) return Chain(predicate(this.value)); - return this; - }, - }; + return { + value: val, + chain(predicate) { + if (this.value !== undefined) return Chain(predicate(this.value)); + return this; + }, + }; }; const MIN_ROW_DISPLAYED = 5; @@ -18,12 +18,12 @@ const COLUMN_KEY = "gallery_columns"; const STRETCHED_KEY = "gallery_stretched"; export default { - name: "texture-page", - components: { - galleryModal, - textureTooltip, - }, - template: ` + name: "texture-page", + components: { + galleryModal, + textureTooltip, + }, + template: `
{{ $root.lang().gallery.title }}
@@ -200,386 +200,386 @@ export default {
`, - data() { - return { - // whether the page shouldn't be stretched to the full width - stretched: localStorage.getItem(STRETCHED_KEY, "true") === "true", - // number of columns you want to display - columns: Number.parseInt(localStorage.getItem(COLUMN_KEY) || 7), - // whether search is loading - loading: false, - // string error extracted - error: undefined, - // search values available - options: { - packs: [], - tags: [this.$root.lang().gallery.all], - versions: settings.versions.java, - editions: settings.editions, - }, - // search values - current: { - pack: "faithful_32x", - tag: "all", - // latest is always at top - version: settings.versions.java[0], - edition: "java", - search: null, - }, - // number of displayed results - displayedResults: 1, - // result - displayedTextures: [], - // loaded contributions - loadedContributions: {}, - // loaded contributors - loadedContributors: {}, - // modal opened ID - modalTextureID: null, - // modal texture opened - modalTextureObj: {}, - // whether modal is opened - modalOpen: false, - packToName: {}, - // styles - styles: { - // gallery cell styles - cell: { - "aspect-ratio": "1", - }, - // grid styles - grid: undefined, - // placeholder font size styles - not_done: { - texture_id: "font-size: 2em;", - texture_name: "font-size: 1.17em;", - message: "font-size: 16px", - }, - }, - // go to the top arrow - scrollY: 0, - }; - }, - computed: { - packList() { - return Object.entries(this.packToName).map(([id, name]) => ({ - label: name, - value: id, - })); - }, - computeEditions() { - return settings.editions.map((e) => ({ label: e, value: e.toLowerCase() })); - }, - displayedTexturesObject() { - return this.displayedTextures.reduce((acc, cur) => { - acc[cur.textureID] = cur; - return acc; - }, {}); - }, - tagItems() { - return this.options.tags.map((e, i) => ({ - label: e, - value: i === 0 ? "all" : e, - })); - }, - }, - watch: { - "$route.params": { - handler(params, old_params) { - // if hash changed but not params - if (JSON.stringify(params) === JSON.stringify(old_params)) return; - - // convert legacy urls to modern format - this.current.pack = ["16x", "32x", "64x"].includes(params.pack) - ? this.resToPackID(params.pack) - : params.pack; - - // change available versions so you don't get a blank item - this.options.versions = settings.versions[params.edition]; - this.current.edition = params.edition; - this.current.version = params.version; - this.current.tag = params.tag; - this.current.search = params.search; - - this.updateSearch(); - }, - deep: true, - immediate: true, - }, - columns(n) { - localStorage.setItem(COLUMN_KEY, String(n)); - this.computeGrid(); - }, - stretched(n) { - localStorage.setItem(STRETCHED_KEY, n); - this.computeGrid(); - }, - modalOpen(n) { - if (!n) this.removeShareURL(); - }, - }, - methods: { - isMojang(packID) { - return ["default", "progart"].includes(packID); - }, - resToPackID(res) { - // for legacy url support - switch (res) { - case "16x": - return "default"; - case "32x": - return "faithful_32x"; - case "64x": - return "faithful_64x"; - } - }, - shareID() { - let index = location.hash.indexOf("?show="); - return index !== -1 ? Number.parseInt(location.hash.substring(index + 6), 10) : undefined; - }, - changeShareURL(id, dry_run = false) { - let index = location.hash.indexOf("?show="); - - let new_hash = location.hash; - // we remove it - if (index !== -1) new_hash = new_hash.substring(0, index); - if (id !== undefined) new_hash += "?show=" + id.toString(); - if (!dry_run) location.hash = new_hash; - - return location.href.replace(location.hash, "") + new_hash; - }, - removeShareURL() { - let index = location.hash.indexOf("?show="); - - let new_hash = location.hash; - // we remove it - if (index !== -1) { - new_hash = new_hash.substring(0, index); - } - - // we change it - location.hash = new_hash; - }, - copyShareLink(id) { - let url = this.changeShareURL(id, true); - navigator.clipboard.writeText(url); - this.$root.showSnackBar(this.$root.lang("gallery.share_link_copied_to_clipboard"), "success"); - }, - checkShare(n) { - if (n === undefined) return; - - this.openModal(n); - }, - openModal(id) { - this.modalTextureID = id; - this.modalTextureObj = {}; // changes text back to loading text if reopening modal - this.modalOpen = true; - - axios.get(`${this.$root.apiURL}/gallery/modal/${id}/${this.current.version}`).then((res) => { - this.modalTextureObj = res.data; - }); - }, - discordIDtoName(d) { - return ( - new Chain(this.loadedContributors[d]).chain( - (c) => c.username || this.$root.lang().gallery.error_message.user_anonymous, - ).value || this.$root.lang().gallery.error_message.user_not_found - ); - }, - timestampToDate(t) { - const a = new Date(t); - return moment(a).format("ll"); - }, - startSearch() { - this.updateRoute(null, null, true); - }, - clearSearch() { - this.updateRoute(null, "search", true); - }, - updateRoute(data, type, force = false) { - if (this.current[type] === data && !force) return; // avoid redundant redirection - - this.current[type] = data; - - // user safe interaction - // check if pack exist - if (!Object.keys(this.packToName).includes(this.current.pack)) - this.current.pack = "faithful_32x"; - - if (!settings.versions[this.current.edition].includes(this.current.version)) { - this.current.version = settings.versions[this.current.edition][0]; - this.options.versions = settings.versions[this.current.edition]; - } - - let route = `/gallery/${this.current.edition}/${this.current.pack}/${this.current.version}/${this.current.tag}`; - route += !this.current.search ? "" : `/${this.current.search}`; - - if (this.$route.path === route) return; // new search is the same as before - return this.$router.push(route); - }, - updateSearch() { - if (this.loading) return; - this.loading = true; - this.displayedTextures = []; - - const version = - this.current.version === "latest" - ? settings.versions[this.current.edition][0] - : this.current.version; - - // /gallery/{pack}/{edition}/{mc_version}/{tag} - axios - .get( - `${this.$root.apiURL}/gallery/${this.current.pack}/${this.current.edition}/${version}/${ - this.current.tag - }${this.current.search ? `?search=${this.current.search}` : ""}`, - ) - .then((res) => { - this.displayedTextures = res.data; - }) - .catch((e) => { - console.error(e); - this.error = `${e.statusCode}: ${ - Chain(e) - .chain((e) => e.response) - .chain((r) => r.message).value - }`; - }) - .finally(() => { - this.loading = false; - }); - }, - scroll() { - window.onscroll = () => { - this.scrollY = document.firstElementChild.scrollTop; - let scrolledTo = document.querySelector(".bottomElement"); - - if (scrolledTo && this.isScrolledIntoView(scrolledTo, 600)) { - this.displayedResults += this.columns * MIN_ROW_DISPLAYED; - this.$forceUpdate(); - } - }; - }, - isScrolledIntoView(el, margin = 0) { - let rect = el.getBoundingClientRect(); - let elemTop = rect.top; - let elemBottom = rect.bottom; - - let isVisible = elemTop < window.innerHeight + margin && elemBottom >= 0; - return isVisible; - }, - toTop() { - window.scrollTo({ - top: 0, - behavior: "smooth", - }); - }, - computeGrid() { - let breakpoints = this.$root.$vuetify.breakpoint; - let gap; - let number; - - let base_columns = this.columns; - - if (breakpoints.smAndDown) { - base_columns = breakpoints.smOnly ? 2 : 1; - } - - // constants - const MIN_WIDTH = 110; - const MARGIN = 20; // .container padding (12px) + .v-list.main-container padding (8px) - - // real content width - const width = this.$el.clientWidth - MARGIN * 2; - - if (base_columns != 1) { - // * We want to solve n * MIN_WIDTH + (n - 1) * A = width - // * where A = 200 / (1.5 * n) - // * => n * MIN_WIDTH + ((n*200)/(1.5*n)) - 1*200/(1.5*n) = width - // * => n * MIN_WIDTH + 200/1.5 - 200/(1.5*n) = width - // * multiply by n - // * => n² * MIN_WIDTH + 200n/1.5 - 200/1.5 = width*n - // * => n² * MIN_WITH + n * (200/1.5 - width) - 200/1.5 = 0 - // * solve that and keep positive value - let a = MIN_WIDTH; - let b = 200 / 1.5 - width; - let c = -200 / 1.5; - let delta = b * b - 4 * a * c; - let n = (-b + Math.sqrt(delta)) / (2 * a); - gap = 200 / (n * 1.5); - number = Math.min(base_columns, Math.floor(n)); - } else { - gap = 8; - number = 1; - } - - const font_size = width / number / 20; - - this.styles.not_done = { - texture_id: { "font-size": `${font_size * 4}px` }, - texture_name: { "font-size": `${font_size * 2}px` }, - message: { "font-size": `${font_size * 1.2}px` }, - }; - - this.styles.grid = { - gap: `${gap}px`, - "grid-template-columns": `repeat(${number}, 1fr)`, - }; - }, - }, - created() { - window.addEventListener("hashchange", () => { - this.checkShare(this.shareID()); - }); - this.checkShare(this.shareID()); - - axios.get(`${this.$root.apiURL}/textures/tags`).then((res) => { - this.options.tags = [...this.options.tags, ...res.data]; - }); - axios.get(`${this.$root.apiURL}/packs/raw`).then((res) => { - this.options.packs = Object.values(res.data).map((v) => v.name); - this.packToName = Object.values(res.data).reduce( - (acc, cur) => ({ - ...acc, - [cur.id]: cur.name, - }), - {}, - ); - }); - axios.get(`${this.$root.apiURL}/contributions/authors`).then((res) => { - this.loadedContributors = res.data.reduce((acc, cur) => { - acc[cur.id] = cur; - return acc; - }, {}); - }); - axios.get(`${this.$root.apiURL}/contributions/raw`).then((res) => { - this.loadedContributions = Object.values(res.data) - .filter((contribution) => contribution.pack && contribution.texture) - .reduce((acc, cur) => { - if (!acc[cur.pack]) acc[cur.pack] = {}; - if (!acc[cur.pack][cur.texture]) acc[cur.pack][cur.texture] = []; - - acc[cur.pack][cur.texture].push({ - contributors: cur.authors, - date: cur.date, - }); - - return acc; - }, {}); - }); - - this.displayedResults = this.columns * MIN_ROW_DISPLAYED; - }, - mounted() { - // redirect from "latest" to actual latest version - const split = this.$route.fullPath.split("/"); - if (split[4] === "latest") { - split[4] = settings.versions[split[2]][0]; - this.$router.push(split.join("/")); - } - - this.scroll(); - window.addEventListener("resize", this.computeGrid); - this.computeGrid(); - }, + data() { + return { + // whether the page shouldn't be stretched to the full width + stretched: localStorage.getItem(STRETCHED_KEY, "true") === "true", + // number of columns you want to display + columns: Number.parseInt(localStorage.getItem(COLUMN_KEY) || 7), + // whether search is loading + loading: false, + // string error extracted + error: undefined, + // search values available + options: { + packs: [], + tags: [this.$root.lang().gallery.all], + versions: settings.versions.java, + editions: settings.editions, + }, + // search values + current: { + pack: "faithful_32x", + tag: "all", + // latest is always at top + version: settings.versions.java[0], + edition: "java", + search: null, + }, + // number of displayed results + displayedResults: 1, + // result + displayedTextures: [], + // loaded contributions + loadedContributions: {}, + // loaded contributors + loadedContributors: {}, + // modal opened ID + modalTextureID: null, + // modal texture opened + modalTextureObj: {}, + // whether modal is opened + modalOpen: false, + packToName: {}, + // styles + styles: { + // gallery cell styles + cell: { + "aspect-ratio": "1", + }, + // grid styles + grid: undefined, + // placeholder font size styles + not_done: { + texture_id: "font-size: 2em;", + texture_name: "font-size: 1.17em;", + message: "font-size: 16px", + }, + }, + // go to the top arrow + scrollY: 0, + }; + }, + computed: { + packList() { + return Object.entries(this.packToName).map(([id, name]) => ({ + label: name, + value: id, + })); + }, + computeEditions() { + return settings.editions.map((e) => ({ label: e, value: e.toLowerCase() })); + }, + displayedTexturesObject() { + return this.displayedTextures.reduce((acc, cur) => { + acc[cur.textureID] = cur; + return acc; + }, {}); + }, + tagItems() { + return this.options.tags.map((e, i) => ({ + label: e, + value: i === 0 ? "all" : e, + })); + }, + }, + watch: { + "$route.params": { + handler(params, old_params) { + // if hash changed but not params + if (JSON.stringify(params) === JSON.stringify(old_params)) return; + + // convert legacy urls to modern format + this.current.pack = ["16x", "32x", "64x"].includes(params.pack) + ? this.resToPackID(params.pack) + : params.pack; + + // change available versions so you don't get a blank item + this.options.versions = settings.versions[params.edition]; + this.current.edition = params.edition; + this.current.version = params.version; + this.current.tag = params.tag; + this.current.search = params.search; + + this.updateSearch(); + }, + deep: true, + immediate: true, + }, + columns(n) { + localStorage.setItem(COLUMN_KEY, String(n)); + this.computeGrid(); + }, + stretched(n) { + localStorage.setItem(STRETCHED_KEY, n); + this.computeGrid(); + }, + modalOpen(n) { + if (!n) this.removeShareURL(); + }, + }, + methods: { + isMojang(packID) { + return ["default", "progart"].includes(packID); + }, + resToPackID(res) { + // for legacy url support + switch (res) { + case "16x": + return "default"; + case "32x": + return "faithful_32x"; + case "64x": + return "faithful_64x"; + } + }, + shareID() { + let index = location.hash.indexOf("?show="); + return index !== -1 ? Number.parseInt(location.hash.substring(index + 6), 10) : undefined; + }, + changeShareURL(id, dry_run = false) { + let index = location.hash.indexOf("?show="); + + let new_hash = location.hash; + // we remove it + if (index !== -1) new_hash = new_hash.substring(0, index); + if (id !== undefined) new_hash += "?show=" + id.toString(); + if (!dry_run) location.hash = new_hash; + + return location.href.replace(location.hash, "") + new_hash; + }, + removeShareURL() { + let index = location.hash.indexOf("?show="); + + let new_hash = location.hash; + // we remove it + if (index !== -1) { + new_hash = new_hash.substring(0, index); + } + + // we change it + location.hash = new_hash; + }, + copyShareLink(id) { + let url = this.changeShareURL(id, true); + navigator.clipboard.writeText(url); + this.$root.showSnackBar(this.$root.lang("gallery.share_link_copied_to_clipboard"), "success"); + }, + checkShare(n) { + if (n === undefined) return; + + this.openModal(n); + }, + openModal(id) { + this.modalTextureID = id; + this.modalTextureObj = {}; // changes text back to loading text if reopening modal + this.modalOpen = true; + + axios.get(`${this.$root.apiURL}/gallery/modal/${id}/${this.current.version}`).then((res) => { + this.modalTextureObj = res.data; + }); + }, + discordIDtoName(d) { + return ( + new Chain(this.loadedContributors[d]).chain( + (c) => c.username || this.$root.lang().gallery.error_message.user_anonymous, + ).value || this.$root.lang().gallery.error_message.user_not_found + ); + }, + timestampToDate(t) { + const a = new Date(t); + return moment(a).format("ll"); + }, + startSearch() { + this.updateRoute(null, null, true); + }, + clearSearch() { + this.updateRoute(null, "search", true); + }, + updateRoute(data, type, force = false) { + if (this.current[type] === data && !force) return; // avoid redundant redirection + + this.current[type] = data; + + // user safe interaction + // check if pack exist + if (!Object.keys(this.packToName).includes(this.current.pack)) + this.current.pack = "faithful_32x"; + + if (!settings.versions[this.current.edition].includes(this.current.version)) { + this.current.version = settings.versions[this.current.edition][0]; + this.options.versions = settings.versions[this.current.edition]; + } + + let route = `/gallery/${this.current.edition}/${this.current.pack}/${this.current.version}/${this.current.tag}`; + route += !this.current.search ? "" : `/${this.current.search}`; + + if (this.$route.path === route) return; // new search is the same as before + return this.$router.push(route); + }, + updateSearch() { + if (this.loading) return; + this.loading = true; + this.displayedTextures = []; + + const version = + this.current.version === "latest" + ? settings.versions[this.current.edition][0] + : this.current.version; + + // /gallery/{pack}/{edition}/{mc_version}/{tag} + axios + .get( + `${this.$root.apiURL}/gallery/${this.current.pack}/${this.current.edition}/${version}/${ + this.current.tag + }${this.current.search ? `?search=${this.current.search}` : ""}`, + ) + .then((res) => { + this.displayedTextures = res.data; + }) + .catch((e) => { + console.error(e); + this.error = `${e.statusCode}: ${ + Chain(e) + .chain((e) => e.response) + .chain((r) => r.message).value + }`; + }) + .finally(() => { + this.loading = false; + }); + }, + scroll() { + window.onscroll = () => { + this.scrollY = document.firstElementChild.scrollTop; + let scrolledTo = document.querySelector(".bottomElement"); + + if (scrolledTo && this.isScrolledIntoView(scrolledTo, 600)) { + this.displayedResults += this.columns * MIN_ROW_DISPLAYED; + this.$forceUpdate(); + } + }; + }, + isScrolledIntoView(el, margin = 0) { + let rect = el.getBoundingClientRect(); + let elemTop = rect.top; + let elemBottom = rect.bottom; + + let isVisible = elemTop < window.innerHeight + margin && elemBottom >= 0; + return isVisible; + }, + toTop() { + window.scrollTo({ + top: 0, + behavior: "smooth", + }); + }, + computeGrid() { + let breakpoints = this.$root.$vuetify.breakpoint; + let gap; + let number; + + let base_columns = this.columns; + + if (breakpoints.smAndDown) { + base_columns = breakpoints.smOnly ? 2 : 1; + } + + // constants + const MIN_WIDTH = 110; + const MARGIN = 20; // .container padding (12px) + .v-list.main-container padding (8px) + + // real content width + const width = this.$el.clientWidth - MARGIN * 2; + + if (base_columns != 1) { + // * We want to solve n * MIN_WIDTH + (n - 1) * A = width + // * where A = 200 / (1.5 * n) + // * => n * MIN_WIDTH + ((n*200)/(1.5*n)) - 1*200/(1.5*n) = width + // * => n * MIN_WIDTH + 200/1.5 - 200/(1.5*n) = width + // * multiply by n + // * => n² * MIN_WIDTH + 200n/1.5 - 200/1.5 = width*n + // * => n² * MIN_WITH + n * (200/1.5 - width) - 200/1.5 = 0 + // * solve that and keep positive value + let a = MIN_WIDTH; + let b = 200 / 1.5 - width; + let c = -200 / 1.5; + let delta = b * b - 4 * a * c; + let n = (-b + Math.sqrt(delta)) / (2 * a); + gap = 200 / (n * 1.5); + number = Math.min(base_columns, Math.floor(n)); + } else { + gap = 8; + number = 1; + } + + const font_size = width / number / 20; + + this.styles.not_done = { + texture_id: { "font-size": `${font_size * 4}px` }, + texture_name: { "font-size": `${font_size * 2}px` }, + message: { "font-size": `${font_size * 1.2}px` }, + }; + + this.styles.grid = { + gap: `${gap}px`, + "grid-template-columns": `repeat(${number}, 1fr)`, + }; + }, + }, + created() { + window.addEventListener("hashchange", () => { + this.checkShare(this.shareID()); + }); + this.checkShare(this.shareID()); + + axios.get(`${this.$root.apiURL}/textures/tags`).then((res) => { + this.options.tags = [...this.options.tags, ...res.data]; + }); + axios.get(`${this.$root.apiURL}/packs/raw`).then((res) => { + this.options.packs = Object.values(res.data).map((v) => v.name); + this.packToName = Object.values(res.data).reduce( + (acc, cur) => ({ + ...acc, + [cur.id]: cur.name, + }), + {}, + ); + }); + axios.get(`${this.$root.apiURL}/contributions/authors`).then((res) => { + this.loadedContributors = res.data.reduce((acc, cur) => { + acc[cur.id] = cur; + return acc; + }, {}); + }); + axios.get(`${this.$root.apiURL}/contributions/raw`).then((res) => { + this.loadedContributions = Object.values(res.data) + .filter((contribution) => contribution.pack && contribution.texture) + .reduce((acc, cur) => { + if (!acc[cur.pack]) acc[cur.pack] = {}; + if (!acc[cur.pack][cur.texture]) acc[cur.pack][cur.texture] = []; + + acc[cur.pack][cur.texture].push({ + contributors: cur.authors, + date: cur.date, + }); + + return acc; + }, {}); + }); + + this.displayedResults = this.columns * MIN_ROW_DISPLAYED; + }, + mounted() { + // redirect from "latest" to actual latest version + const split = this.$route.fullPath.split("/"); + if (split[4] === "latest") { + split[4] = settings.versions[split[2]][0]; + this.$router.push(split.join("/")); + } + + this.scroll(); + window.addEventListener("resize", this.computeGrid); + this.computeGrid(); + }, }; diff --git a/pages/gallery/gallery_tooltip.js b/pages/gallery/gallery_tooltip.js index dff16ce..5b39944 100644 --- a/pages/gallery/gallery_tooltip.js +++ b/pages/gallery/gallery_tooltip.js @@ -1,38 +1,38 @@ const Chain = function (val) { - return { - value: val, - chain(predicate) { - if (this.value !== undefined) return Chain(predicate(this.value)); - return this; - }, - }; + return { + value: val, + chain(predicate) { + if (this.value !== undefined) return Chain(predicate(this.value)); + return this; + }, + }; }; export default { - name: "texture-tooltip", - props: { - texture: { - type: Object, - required: true, - }, - mojang: { - type: Boolean, - required: true, - }, - contributions: { - type: Object, - required: true, - }, - pack: { - type: String, - required: true, - }, - discordIDtoName: { - type: Function, - required: true, - }, - }, - template: ` + name: "texture-tooltip", + props: { + texture: { + type: Object, + required: true, + }, + mojang: { + type: Boolean, + required: true, + }, + contributions: { + type: Object, + required: true, + }, + pack: { + type: String, + required: true, + }, + discordIDtoName: { + type: Function, + required: true, + }, + }, + template: `
@@ -55,40 +55,40 @@ export default {
`, - computed: { - last_contribution() { - let contribs = Chain(this.contributions) - .chain((contribs) => contribs[this.pack]) - .chain((res_contribs) => res_contribs[this.texture.textureID]).value; + computed: { + last_contribution() { + let contribs = Chain(this.contributions) + .chain((contribs) => contribs[this.pack]) + .chain((res_contribs) => res_contribs[this.texture.textureID]).value; - // get best timestamp contrib - return contribs - ? contribs.reduce((a, b) => (a = a.date > b.date ? a : b), contribs[0]) - : undefined; - }, - last_contribution_names() { - if (this.last_contribution === undefined) return ""; - return this.last_contribution.contributors - .map((d) => { - return this.discordIDtoName(d).replace(/\s/gm, "\u00A0"); - }) - .join(", "); - }, - icon() { - return "icon-people" + (this.last_contribution.contributors.length === 1 ? "" : "s"); - }, - modded() { - let something_with_path = this.texture.url; - return ["assets/forge", "assets/fml", "assets/fabric", "assets/modmenu"].reduce( - (acc, cur) => acc || something_with_path.includes(cur), - false, - ); - }, - }, - methods: { - timestampToDate(t) { - const a = new Date(t); - return moment(a).format("ll"); - }, - }, + // get best timestamp contrib + return contribs + ? contribs.reduce((a, b) => (a = a.date > b.date ? a : b), contribs[0]) + : undefined; + }, + last_contribution_names() { + if (this.last_contribution === undefined) return ""; + return this.last_contribution.contributors + .map((d) => { + return this.discordIDtoName(d).replace(/\s/gm, "\u00A0"); + }) + .join(", "); + }, + icon() { + return "icon-people" + (this.last_contribution.contributors.length === 1 ? "" : "s"); + }, + modded() { + let something_with_path = this.texture.url; + return ["assets/forge", "assets/fml", "assets/fabric", "assets/modmenu"].reduce( + (acc, cur) => acc || something_with_path.includes(cur), + false, + ); + }, + }, + methods: { + timestampToDate(t) { + const a = new Date(t); + return moment(a).format("ll"); + }, + }, }; diff --git a/pages/gallery/modal.js b/pages/gallery/modal.js index eb8d040..711515a 100644 --- a/pages/gallery/modal.js +++ b/pages/gallery/modal.js @@ -1,8 +1,8 @@ /* global axios, Vue */ export default { - name: "gallery-modal", - template: ` + name: "gallery-modal", + template: ` `, - props: { - value: { - type: Boolean, - required: true, - }, - textureID: { - required: true, - }, - textureObj: { - type: Object, - required: true, - }, - contributors: { - type: Object, - required: true, - }, - // saves on duplicate code, since it's already in the gallery page - packToName: { - type: Object, - required: true, - }, - onClose: { - type: Function, - default: () => {}, - }, - }, - data() { - return { - resolutions: ["16x", ...settings.resolutions], - tab: null, - items: [ - this.$root.lang().gallery.modal.items.information, - this.$root.lang().gallery.modal.items.authors, - ], - infos: ["texture", "uses", "paths"], - authors: settings.resolutions, - opened: false, - }; - }, - watch: { - value: { - handler(n) { - this.opened = n; - }, - immediate: true, - }, - opened(n) { - this.$emit("input", n); - }, - }, - methods: { - closeModal() { - this.onClose(); - this.opened = false; - }, - discordIDtoName(d) { - return this.contributors[d] - ? this.contributors[d].username - ? this.contributors[d].username - : this.$root.lang().gallery.error_message.user_anonymous - : this.$root.lang().gallery.error_message.user_not_found; - }, - timestampToDate(t) { - const a = new Date(t); - return moment(a).format("ll"); - }, - getItems(item) { - let output = []; + props: { + value: { + type: Boolean, + required: true, + }, + textureID: { + required: true, + }, + textureObj: { + type: Object, + required: true, + }, + contributors: { + type: Object, + required: true, + }, + // saves on duplicate code, since it's already in the gallery page + packToName: { + type: Object, + required: true, + }, + onClose: { + type: Function, + default: () => {}, + }, + }, + data() { + return { + resolutions: ["16x", ...settings.resolutions], + tab: null, + items: [ + this.$root.lang().gallery.modal.items.information, + this.$root.lang().gallery.modal.items.authors, + ], + infos: ["texture", "uses", "paths"], + authors: settings.resolutions, + opened: false, + }; + }, + watch: { + value: { + handler(n) { + this.opened = n; + }, + immediate: true, + }, + opened(n) { + this.$emit("input", n); + }, + }, + methods: { + closeModal() { + this.onClose(); + this.opened = false; + }, + discordIDtoName(d) { + return this.contributors[d] + ? this.contributors[d].username + ? this.contributors[d].username + : this.$root.lang().gallery.error_message.user_anonymous + : this.$root.lang().gallery.error_message.user_not_found; + }, + timestampToDate(t) { + const a = new Date(t); + return moment(a).format("ll"); + }, + getItems(item) { + let output = []; - switch (item) { - case this.authors[0]: - case this.authors[1]: - return this.textureObj.contributions - .filter((el) => el.resolution === parseInt(item, 10)) - .sort((a, b) => b.date - a.date) - .map((el) => ({ - date: this.timestampToDate(el.date), - pack: this.packToName[el.pack], - contributors: el.authors.map((el) => this.discordIDtoName(el)).join(",\n"), - })); - case this.infos[0]: - return [ - { - ...this.textureObj[item], - tags: this.textureObj[item].tags.join(", "), - }, - ]; - case this.infos[1]: - return Object.values(this.textureObj[item]); + switch (item) { + case this.authors[0]: + case this.authors[1]: + return this.textureObj.contributions + .filter((el) => el.resolution === parseInt(item, 10)) + .sort((a, b) => b.date - a.date) + .map((el) => ({ + date: this.timestampToDate(el.date), + pack: this.packToName[el.pack], + contributors: el.authors.map((el) => this.discordIDtoName(el)).join(",\n"), + })); + case this.infos[0]: + return [ + { + ...this.textureObj[item], + tags: this.textureObj[item].tags.join(", "), + }, + ]; + case this.infos[1]: + return Object.values(this.textureObj[item]); - case this.infos[2]: - this.textureObj[item].forEach((path) => { - output.push({ - ...path, - versions: path.versions.join(", "), - }); - }); + case this.infos[2]: + this.textureObj[item].forEach((path) => { + output.push({ + ...path, + versions: path.versions.join(", "), + }); + }); - return output; - } - }, - getHeaders(item) { - switch (item) { - case this.authors[0]: - case this.authors[1]: - return [ - { - text: this.$root.lang().gallery.modal.tabs.date, - value: "date", - }, - { - text: this.$root.lang("gallery.modal.tabs.pack"), - value: "pack", - }, - { - text: this.$root.lang().gallery.modal.tabs.authors, - value: "contributors", - }, - ]; - case this.infos[0]: - return [ - { - text: this.$root.lang().gallery.modal.tabs.id, - value: "id", - sortable: false, - }, - { - text: this.$root.lang().gallery.modal.tabs.name, - value: "name", - sortable: false, - }, - { - text: this.$root.lang().gallery.modal.tabs.tags, - value: "tags", - sortable: false, - }, - ]; - case this.infos[1]: - return [ - { - text: this.$root.lang().gallery.modal.tabs.use_id, - value: "id", - }, - { - text: this.$root.lang().gallery.modal.tabs.use_name, - value: "name", - }, - { - text: this.$root.lang().gallery.modal.tabs.editions, - value: "edition", - }, - { - text: this.$root.lang().gallery.modal.tabs.texture_id, - value: "texture", - }, - ]; + return output; + } + }, + getHeaders(item) { + switch (item) { + case this.authors[0]: + case this.authors[1]: + return [ + { + text: this.$root.lang().gallery.modal.tabs.date, + value: "date", + }, + { + text: this.$root.lang("gallery.modal.tabs.pack"), + value: "pack", + }, + { + text: this.$root.lang().gallery.modal.tabs.authors, + value: "contributors", + }, + ]; + case this.infos[0]: + return [ + { + text: this.$root.lang().gallery.modal.tabs.id, + value: "id", + sortable: false, + }, + { + text: this.$root.lang().gallery.modal.tabs.name, + value: "name", + sortable: false, + }, + { + text: this.$root.lang().gallery.modal.tabs.tags, + value: "tags", + sortable: false, + }, + ]; + case this.infos[1]: + return [ + { + text: this.$root.lang().gallery.modal.tabs.use_id, + value: "id", + }, + { + text: this.$root.lang().gallery.modal.tabs.use_name, + value: "name", + }, + { + text: this.$root.lang().gallery.modal.tabs.editions, + value: "edition", + }, + { + text: this.$root.lang().gallery.modal.tabs.texture_id, + value: "texture", + }, + ]; - case this.infos[2]: - return [ - { - text: this.$root.lang().gallery.modal.tabs.path_id, - value: "id", - }, - { - text: this.$root.lang().gallery.modal.tabs.resource_pack_path, - value: "name", - }, - { - text: this.$root.lang().gallery.modal.tabs.mc_versions, - value: "versions", - }, - { - text: this.$root.lang().gallery.modal.tabs.use_id, - value: "use", - }, - ]; - } - }, - toTitleCase(text) { - return text[0].toUpperCase() + text.substring(1); - }, - }, - computed: { - infosText() { - return { - texture: this.toTitleCase(this.$root.lang().gallery.modal.infos.texture), - uses: this.toTitleCase(this.$root.lang().gallery.modal.infos.uses), - paths: this.toTitleCase(this.$root.lang().gallery.modal.infos.paths), - }; - }, - grouped() { - const result = []; - if (this.textureObj) { - Object.entries(this.textureObj.urls).forEach((urlArr, i) => { - if (i % 2 === 0) result.push([]); - result[result.length - 1].push(urlArr); - }); - } + case this.infos[2]: + return [ + { + text: this.$root.lang().gallery.modal.tabs.path_id, + value: "id", + }, + { + text: this.$root.lang().gallery.modal.tabs.resource_pack_path, + value: "name", + }, + { + text: this.$root.lang().gallery.modal.tabs.mc_versions, + value: "versions", + }, + { + text: this.$root.lang().gallery.modal.tabs.use_id, + value: "use", + }, + ]; + } + }, + toTitleCase(text) { + return text[0].toUpperCase() + text.substring(1); + }, + }, + computed: { + infosText() { + return { + texture: this.toTitleCase(this.$root.lang().gallery.modal.infos.texture), + uses: this.toTitleCase(this.$root.lang().gallery.modal.infos.uses), + paths: this.toTitleCase(this.$root.lang().gallery.modal.infos.paths), + }; + }, + grouped() { + const result = []; + if (this.textureObj) { + Object.entries(this.textureObj.urls).forEach((urlArr, i) => { + if (i % 2 === 0) result.push([]); + result[result.length - 1].push(urlArr); + }); + } - return result; - }, - }, + return result; + }, + }, }; diff --git a/pages/pack/main.js b/pages/pack/main.js index ff8b4c5..861e2ab 100644 --- a/pages/pack/main.js +++ b/pages/pack/main.js @@ -1 +1,12 @@ -export default {}; +/* global axios, Vue */ + +export default { + name: "pack-page", + template: ` + +
+ {{ $root.lang().database.titles.packs }} +
+
+ `, +}; diff --git a/pages/profile/main.js b/pages/profile/main.js index 972b819..1785c72 100644 --- a/pages/profile/main.js +++ b/pages/profile/main.js @@ -1,8 +1,8 @@ /* global axios */ export default { - name: "profile-page", - template: ` + name: "profile-page", + template: `
{{ $root.lang().profile.title }} @@ -183,126 +183,126 @@ export default {
`, - data() { - return { - uuidMaxLength: 36, - usernameMaxLength: 24, - everythingIsOk: false, - newMedia: { - type: "", - link: "", - }, - media: settings.socials, - urlAddRules: [(u) => this.validForm(this.validURL(u) || u === "", "URL must be valid.")], - urlRules: [(u) => this.validForm(this.validURL(u), "URL must be valid.")], - uuidRules: [ - (u) => - this.validForm( - (u && u.length === this.uuidMaxLength) || !u, - "The UUID needs to be 36 characters long.", - ), - ], - usernameRules: [ - (u) => this.validForm(!!u, "Username is required."), - (u) => - this.validForm( - u && typeof u === "string" && u.trim().length > 0, - `Username cannot be empty`, - ), - (u) => - this.validForm( - u && u.length <= this.usernameMaxLength, - `Username must be less than ${this.usernameMaxLength} characters.`, - ), - ], - localUser: {}, - }; - }, - methods: { - isMediaOk() { - if (this.newMedia.type !== "" && this.newMedia.link !== "") return false; - return true; - }, - validURL(str) { - const pattern = new RegExp( - "^(https?:\\/\\/)?" + // protocol - "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name - "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address - "(\\:\\d+)?(\\/[-a-z\\d%_.~+@]*)*" + // port and path - "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string - "(\\#[-a-z\\d_]*)?$", - "i", - ); // fragment locator - return !!pattern.test(str); - }, - removeSocialMedia(index) { - this.localUser.media.splice(index, 1); - }, - addSocialMedia() { - if (!this.localUser.media) this.localUser.media = []; + data() { + return { + uuidMaxLength: 36, + usernameMaxLength: 24, + everythingIsOk: false, + newMedia: { + type: "", + link: "", + }, + media: settings.socials, + urlAddRules: [(u) => this.validForm(this.validURL(u) || u === "", "URL must be valid.")], + urlRules: [(u) => this.validForm(this.validURL(u), "URL must be valid.")], + uuidRules: [ + (u) => + this.validForm( + (u && u.length === this.uuidMaxLength) || !u, + "The UUID needs to be 36 characters long.", + ), + ], + usernameRules: [ + (u) => this.validForm(!!u, "Username is required."), + (u) => + this.validForm( + u && typeof u === "string" && u.trim().length > 0, + `Username cannot be empty`, + ), + (u) => + this.validForm( + u && u.length <= this.usernameMaxLength, + `Username must be less than ${this.usernameMaxLength} characters.`, + ), + ], + localUser: {}, + }; + }, + methods: { + isMediaOk() { + if (this.newMedia.type !== "" && this.newMedia.link !== "") return false; + return true; + }, + validURL(str) { + const pattern = new RegExp( + "^(https?:\\/\\/)?" + // protocol + "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name + "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address + "(\\:\\d+)?(\\/[-a-z\\d%_.~+@]*)*" + // port and path + "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string + "(\\#[-a-z\\d_]*)?$", + "i", + ); // fragment locator + return !!pattern.test(str); + }, + removeSocialMedia(index) { + this.localUser.media.splice(index, 1); + }, + addSocialMedia() { + if (!this.localUser.media) this.localUser.media = []; - this.localUser.media.push({ - type: this.newMedia.type, - link: this.newMedia.link, - }); + this.localUser.media.push({ + type: this.newMedia.type, + link: this.newMedia.link, + }); - this.newMedia = { - type: "", - link: "", - }; - }, - validForm(boolResult, sentence) { - if (boolResult) { - this.everythingIsOk = true; - return true; - } + this.newMedia = { + type: "", + link: "", + }; + }, + validForm(boolResult, sentence) { + if (boolResult) { + this.everythingIsOk = true; + return true; + } - this.everythingIsOk = false; - return sentence.toString(); - }, - send() { - if (!this.$root.isUserLogged) return; + this.everythingIsOk = false; + return sentence.toString(); + }, + send() { + if (!this.$root.isUserLogged) return; - // fix if new user - const data = { - uuid: this.localUser.uuid || "", - username: this.localUser.username || "", - media: this.localUser.media || [], - }; + // fix if new user + const data = { + uuid: this.localUser.uuid || "", + username: this.localUser.username || "", + media: this.localUser.media || [], + }; - axios - .post(`${this.$root.apiURL}/users/profile/`, data, this.$root.apiOptions) - .then(() => { - this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); - }) - .catch((error) => { - console.error(error); - this.$root.showSnackBar(error, "error"); - }); - }, - getUserInfo() { - if (!this.$root.isUserLogged) return; + axios + .post(`${this.$root.apiURL}/users/profile/`, data, this.$root.apiOptions) + .then(() => { + this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); + }) + .catch((error) => { + console.error(error); + this.$root.showSnackBar(error, "error"); + }); + }, + getUserInfo() { + if (!this.$root.isUserLogged) return; - axios - .get(`${this.$root.apiURL}/users/profile/`, this.$root.apiOptions) - .then((res) => { - this.localUser = res.data; + axios + .get(`${this.$root.apiURL}/users/profile/`, this.$root.apiOptions) + .then((res) => { + this.localUser = res.data; - // fix if new user or empty user - this.localUser.uuid = this.localUser.uuid || ""; - this.localUser.username = this.localUser.username || ""; - this.localUser.media = this.localUser.media || []; - }) - .catch((err) => { - console.error(err); - this.$root.showSnackBar(err, "error"); - }); - }, - update() { - this.getUserInfo(); - }, - }, - mounted() { - this.$root.addAccessTokenListener(this.update); - }, + // fix if new user or empty user + this.localUser.uuid = this.localUser.uuid || ""; + this.localUser.username = this.localUser.username || ""; + this.localUser.media = this.localUser.media || []; + }) + .catch((err) => { + console.error(err); + this.$root.showSnackBar(err, "error"); + }); + }, + update() { + this.getUserInfo(); + }, + }, + mounted() { + this.$root.addAccessTokenListener(this.update); + }, }; diff --git a/pages/reconnect/reconnect.js b/pages/reconnect/reconnect.js index 280ae78..84c0a5e 100644 --- a/pages/reconnect/reconnect.js +++ b/pages/reconnect/reconnect.js @@ -1,6 +1,6 @@ export default { - name: "reconnect-page", - template: ` + name: "reconnect-page", + template: ` { - this.reconnect_steps.push(this.$root.lang("reconnect.dummy_step")); - return response.data; - }) - .then(async (json) => { - console.log(json); - this.reconnect_steps.push(this.$root.lang("reconnect.updating_profile_information")); + axios + .post("/api/discord/refresh", data) + .then((response) => { + this.reconnect_steps.push(this.$root.lang("reconnect.dummy_step")); + return response.data; + }) + .then(async (json) => { + console.log(json); + this.reconnect_steps.push(this.$root.lang("reconnect.updating_profile_information")); - await this.$root.tokenCallback(json, auth); + await this.$root.tokenCallback(json, auth); - return fetch("https://discord.com/api/users/@me", { - headers: { - authorization: `Bearer ${json.access_token}`, - }, - }); - }) - .then((response) => { - if (response.ok) return response.json(); - else return Promise.reject(`Failed to update information`); - }) - .then(async (json) => { - auth.id = json.id; - auth.avatar = - json.avatar !== null - ? `https://cdn.discordapp.com/avatars/${json.id}/${json.avatar}?size=1024` - : null; - auth.banner = - json.banner != null - ? `https://cdn.discordapp.com/banners/${json.id}/${json.banner}?size=1024` - : "https://database.faithfulpack.net/images/branding/backgrounds/forest.png"; - auth.username = - json.discriminator != 0 ? json.username + "#" + json.discriminator : json.global_name; + return fetch("https://discord.com/api/users/@me", { + headers: { + authorization: `Bearer ${json.access_token}`, + }, + }); + }) + .then((response) => { + if (response.ok) return response.json(); + else return Promise.reject(`Failed to update information`); + }) + .then(async (json) => { + auth.id = json.id; + auth.avatar = + json.avatar !== null + ? `https://cdn.discordapp.com/avatars/${json.id}/${json.avatar}?size=1024` + : null; + auth.banner = + json.banner != null + ? `https://cdn.discordapp.com/banners/${json.id}/${json.banner}?size=1024` + : "https://database.faithfulpack.net/images/branding/backgrounds/forest.png"; + auth.username = + json.discriminator != 0 ? json.username + "#" + json.discriminator : json.global_name; - await this.$root.tokenCallback(auth, auth); - }) - .then(() => { - // working but not perfect, will refresh page and reload $root.user with correct data - window.location.href = window.location.origin; - }) - .catch((err) => { - console.error(err); - this.$root.showSnackBar(err, "error"); + await this.$root.tokenCallback(auth, auth); + }) + .then(() => { + // working but not perfect, will refresh page and reload $root.user with correct data + window.location.href = window.location.origin; + }) + .catch((err) => { + console.error(err); + this.$root.showSnackBar(err, "error"); - this.reconnect_steps.push(this.$root.lang("reconnect.updating_profile_information")); - this.reconnect_steps.push(this.$root.lang("reconnect.updating_profile_information")); + this.reconnect_steps.push(this.$root.lang("reconnect.updating_profile_information")); + this.reconnect_steps.push(this.$root.lang("reconnect.updating_profile_information")); - this.$root.logout(); - }); - }, + this.$root.logout(); + }); + }, }; diff --git a/pages/review/deny_popup.js b/pages/review/deny_popup.js index 710cab7..fe2f799 100644 --- a/pages/review/deny_popup.js +++ b/pages/review/deny_popup.js @@ -1,6 +1,6 @@ export default { - name: "deny-popup", - template: ` + name: "deny-popup", + template: ` `, - props: { - reasonPopup: { - type: Boolean, - required: true, - }, - closePopup: { - type: Function, - required: true, - }, - }, - data() { - return { - denyReason: "", - reasonRules: [(u) => !u || u?.length > 0 || this.$root.lang().review.deny_window.rule], - }; - }, + props: { + reasonPopup: { + type: Boolean, + required: true, + }, + closePopup: { + type: Function, + required: true, + }, + }, + data() { + return { + denyReason: "", + reasonRules: [(u) => !u || u?.length > 0 || this.$root.lang().review.deny_window.rule], + }; + }, }; diff --git a/pages/review/expansion_panel.js b/pages/review/expansion_panel.js index 04f1589..e8bb3f2 100644 --- a/pages/review/expansion_panel.js +++ b/pages/review/expansion_panel.js @@ -2,12 +2,12 @@ const FullscreenPreview = () => import("../addon/fullscreen-preview.js"); const ImagePreviewer = () => import("../addon/image-previewer.js"); export default { - name: "exp-panel", - components: { - ImagePreviewer, - FullscreenPreview, - }, - template: ` + name: "exp-panel", + components: { + ImagePreviewer, + FullscreenPreview, + }, + template: ` `, - props: { - addons: { - type: Array, - required: true, - }, - reviewAddon: { - type: Function, - required: true, - }, - openDenyPopup: { - type: Function, - required: true, - }, - contributors: { - type: Array, - required: true, - }, - update: { - type: Function, - required: true, - }, - status: { - type: String, - required: true, - }, - color: { - type: String, - required: false, - default: "primary", - }, - value: { - type: String, - required: true, - }, - }, - data() { - return { - imagePreview: "", - dialogAddon: {}, - dialogOpen: false, + props: { + addons: { + type: Array, + required: true, + }, + reviewAddon: { + type: Function, + required: true, + }, + openDenyPopup: { + type: Function, + required: true, + }, + contributors: { + type: Array, + required: true, + }, + update: { + type: Function, + required: true, + }, + status: { + type: String, + required: true, + }, + color: { + type: String, + required: false, + default: "primary", + }, + value: { + type: String, + required: true, + }, + }, + data() { + return { + imagePreview: "", + dialogAddon: {}, + dialogOpen: false, - addonInPanelLoading: true, - addonInPanel: {}, - addonURL: undefined, - addonInPanelHeaderURL: undefined, - }; - }, - computed: { - addonSources() { - return (this.addonInPanel.files || []) - .filter((f) => f.use === "carousel" || f.use === "screenshot") - .map((f) => f.source); - }, - }, - methods: { - getAddon(id) { - this.addonInPanelLoading = true; + addonInPanelLoading: true, + addonInPanel: {}, + addonURL: undefined, + addonInPanelHeaderURL: undefined, + }; + }, + computed: { + addonSources() { + return (this.addonInPanel.files || []) + .filter((f) => f.use === "carousel" || f.use === "screenshot") + .map((f) => f.source); + }, + }, + methods: { + getAddon(id) { + this.addonInPanelLoading = true; - this.$emit("input", id); + this.$emit("input", id); - // allSettled if no header res - Promise.allSettled([ - axios.get(`${this.$root.apiURL}/addons/${id}/all`, this.$root.apiOptions), - axios.get(`${this.$root.apiURL}/addons/${id}/files/header`, this.$root.apiOptions), - ]).then(([res, header_res]) => { - // void value if already here (closing tab) - if (this.addonInPanel.id === res.value.data.id) { - this.addonInPanel = {}; - this.addonInPanelLoading = true; - return; - } + // allSettled if no header res + Promise.allSettled([ + axios.get(`${this.$root.apiURL}/addons/${id}/all`, this.$root.apiOptions), + axios.get(`${this.$root.apiURL}/addons/${id}/files/header`, this.$root.apiOptions), + ]).then(([res, header_res]) => { + // void value if already here (closing tab) + if (this.addonInPanel.id === res.value.data.id) { + this.addonInPanel = {}; + this.addonInPanelLoading = true; + return; + } - this.addonInPanel = res.value.data; - this.addonInPanelLoading = false; + this.addonInPanel = res.value.data; + this.addonInPanelLoading = false; - if (header_res.value) - this.addonInPanelHeaderURL = header_res.value.data + "?t=" + new Date().getTime(); - else this.addonInPanelHeaderURL = null; - }); - }, - openDialog() { - this.dialogAddon = this.addonInPanel; - this.dialogOpen = true; - }, - closeDialog() { - this.dialogOpen = false; - this.dialogAddon = {}; - this.update(); - }, - getUsername(id) { - if (id === null || id === undefined) return "Herobrine"; - return this.contributors.filter((c) => c.id === id)[0].username || "Unknown User"; - }, - }, - mounted() { - let found_addon = this.addons.find((a) => a.id === this.value); + if (header_res.value) + this.addonInPanelHeaderURL = header_res.value.data + "?t=" + new Date().getTime(); + else this.addonInPanelHeaderURL = null; + }); + }, + openDialog() { + this.dialogAddon = this.addonInPanel; + this.dialogOpen = true; + }, + closeDialog() { + this.dialogOpen = false; + this.dialogAddon = {}; + this.update(); + }, + getUsername(id) { + if (id === null || id === undefined) return "Herobrine"; + return this.contributors.filter((c) => c.id === id)[0].username || "Unknown User"; + }, + }, + mounted() { + let found_addon = this.addons.find((a) => a.id === this.value); - if (found_addon) { - const refs = this.$refs[this.value]; - if (refs === undefined) return; + if (found_addon) { + const refs = this.$refs[this.value]; + if (refs === undefined) return; - const ref = refs[0]; - ref.$children[0].$el.click(); - } - }, + const ref = refs[0]; + ref.$children[0].$el.click(); + } + }, }; diff --git a/pages/review/review_addons.js b/pages/review/review_addons.js index 10e48d5..309f17e 100644 --- a/pages/review/review_addons.js +++ b/pages/review/review_addons.js @@ -7,70 +7,70 @@ const ReviewList = () => import("./review_list.js"); const ReviewPreview = () => import("./review_previewer.js"); const searchMixin = { - methods: { - /** - * Loads all search params - * @returns {URLSearchParams} - */ - search_load() { - const query_str = location.hash.split("?")[1] || ""; - return new URLSearchParams(query_str); - }, - /** - * Gets a specific param - * @param {string} name Search param name - * @returns {String|null} param value - */ - search_get(name) { - return this.load().get(name); - }, - /** - * Updates search param with new - * @param {string} name Search param name - * @param {any} value given value - */ - search_set(name, value) { - const str_val = String(value); + methods: { + /** + * Loads all search params + * @returns {URLSearchParams} + */ + search_load() { + const query_str = location.hash.split("?")[1] || ""; + return new URLSearchParams(query_str); + }, + /** + * Gets a specific param + * @param {string} name Search param name + * @returns {String|null} param value + */ + search_get(name) { + return this.load().get(name); + }, + /** + * Updates search param with new + * @param {string} name Search param name + * @param {any} value given value + */ + search_set(name, value) { + const str_val = String(value); - const loaded = this.search_load(); - loaded.set(name, str_val); + const loaded = this.search_load(); + loaded.set(name, str_val); - this._search_update(loaded); - }, - search_delete(name) { - const loaded = this.search_load(); + this._search_update(loaded); + }, + search_delete(name) { + const loaded = this.search_load(); - loaded.delete(name); + loaded.delete(name); - this._search_update(loaded); - }, - /** - * update hash search - * @param {URLSearchParams} search_params updated params - */ - _search_update(search_params) { - let query_str = "?" + search_params.toString(); + this._search_update(loaded); + }, + /** + * update hash search + * @param {URLSearchParams} search_params updated params + */ + _search_update(search_params) { + let query_str = "?" + search_params.toString(); - let hash = location.hash; - if (hash.indexOf("?") !== -1) hash = hash.substring(0, hash.indexOf("?")); - hash += query_str; + let hash = location.hash; + if (hash.indexOf("?") !== -1) hash = hash.substring(0, hash.indexOf("?")); + hash += query_str; - location.hash = hash; - }, - }, + location.hash = hash; + }, + }, }; export default { - name: "review-addons-page", - components: { - ExpPanel, - DenyPopup, - ReviewCategories, - ReviewList, - ReviewPreview, - }, - mixins: [searchMixin], - template: ` + name: "review-addons-page", + components: { + ExpPanel, + DenyPopup, + ReviewCategories, + ReviewList, + ReviewPreview, + }, + mixins: [searchMixin], + template: `
@@ -142,189 +142,189 @@ d88 888 888 888 d88 888
`, - data() { - return { - pageColor: "deep-purple lighten-2", - pageStyles: "", - textColorOnPage: "white--text", - colors: { - pending: "yellow", - denied: "red", - approved: "teal", - archived: "grey", - }, - addons: { - pending: [], - denied: [], - approved: [], - archived: [], - }, - loading: { - pending: true, - denied: true, - approved: true, - archived: true, - }, - contributors: [], + data() { + return { + pageColor: "deep-purple lighten-2", + pageStyles: "", + textColorOnPage: "white--text", + colors: { + pending: "yellow", + denied: "red", + approved: "teal", + archived: "grey", + }, + addons: { + pending: [], + denied: [], + approved: [], + archived: [], + }, + loading: { + pending: true, + denied: true, + approved: true, + archived: true, + }, + contributors: [], - showDenyPopup: false, - denyAddon: {}, - archive: false, - status: "pending", - selectedAddonId: undefined, - }; - }, - watch: { - status(n) { - // select first if not empty - this.search_set("status", n); - this.selectedAddonId = this.addons[n].length > 0 ? this.addons[n][0].id : undefined; - }, - selectedAddonId(n) { - if (n !== undefined) this.search_set("id", n); - else this.search_delete("id"); - }, - }, - computed: { - stats() { - return Object.values(this.addons).map((v) => v.length); - }, - categories() { - return Object.keys(this.addons).map((s, i) => { - return { - label: this.$root.lang(`review.titles.${s}`), - color: this.colors[s], - value: s, - count: this.stats[i] !== undefined ? String(this.stats[i]) : "", - }; - }); - }, - items() { - return Object.entries(this.addons) - .map(([state, list]) => { - return { - state, - items: list.map((addon) => { - return { - key: String(addon.id), - primary: addon.name, - secondary: addon.options.tags.join(" | "), - }; - }), - }; - }) - .reduce((acc, cur) => { - acc[cur.state] = cur.items; - return acc; - }, {}); - }, - selectedItems() { - return this.items[this.status]; - }, - empty() { - return this.$root.lang(`review.labels.${this.status}`); - }, - }, - methods: { - reviewAddon(addon, status, reason = null) { - const id = typeof addon === "object" ? addon.id : addon; - if (!this.$root.isUserLogged) return; + showDenyPopup: false, + denyAddon: {}, + archive: false, + status: "pending", + selectedAddonId: undefined, + }; + }, + watch: { + status(n) { + // select first if not empty + this.search_set("status", n); + this.selectedAddonId = this.addons[n].length > 0 ? this.addons[n][0].id : undefined; + }, + selectedAddonId(n) { + if (n !== undefined) this.search_set("id", n); + else this.search_delete("id"); + }, + }, + computed: { + stats() { + return Object.values(this.addons).map((v) => v.length); + }, + categories() { + return Object.keys(this.addons).map((s, i) => { + return { + label: this.$root.lang(`review.titles.${s}`), + color: this.colors[s], + value: s, + count: this.stats[i] !== undefined ? String(this.stats[i]) : "", + }; + }); + }, + items() { + return Object.entries(this.addons) + .map(([state, list]) => { + return { + state, + items: list.map((addon) => { + return { + key: String(addon.id), + primary: addon.name, + secondary: addon.options.tags.join(" | "), + }; + }), + }; + }) + .reduce((acc, cur) => { + acc[cur.state] = cur.items; + return acc; + }, {}); + }, + selectedItems() { + return this.items[this.status]; + }, + empty() { + return this.$root.lang(`review.labels.${this.status}`); + }, + }, + methods: { + reviewAddon(addon, status, reason = null) { + const id = typeof addon === "object" ? addon.id : addon; + if (!this.$root.isUserLogged) return; - const data = { - status: status, - reason: reason, - }; + const data = { + status: status, + reason: reason, + }; - axios - .put(`${this.$root.apiURL}/addons/${id}/review`, data, this.$root.apiOptions) - .then(() => { - this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); - this.selectedAddonId = undefined; - this.update(); - }) - .catch((err) => { - console.error(err); - this.$root.showSnackBar(err, "error"); - }); - }, - closeDenyPopup(send = false, reason) { - this.showDenyPopup = false; - if (send) this.reviewAddon(this.denyAddon, this.archive ? "archived" : "denied", reason); - }, - openDenyPopup(addon, archive = undefined) { - this.archive = !!archive; - this.showDenyPopup = true; - this.denyAddon = addon; - }, - getAddonsByStatus(status) { - return axios - .get(`${this.$root.apiURL}/addons/${status}`, this.$root.apiOptions) - .then((res) => { - this.addons[status] = res.data; - this.addons[status].forEach((addon) => (addon.options.tags = addon.options.tags.sort())); - this.loading[status] = false; - this.$forceUpdate(); - }) - .catch((err) => { - console.error(err); - this.$root.showSnackBar(err, "error"); - }); - }, - getContributors() { - axios - .get(`${this.$root.apiURL}/users/names`) - .then((res) => { - this.contributors = res.data; - }) - .catch((err) => { - console.error(err); - this.$root.showSnackBar(err, "error"); - }); - }, - update() { - Promise.all([ - this.getContributors(), - this.getAddonsByStatus("pending"), - this.getAddonsByStatus("denied"), - this.getAddonsByStatus("approved"), - this.getAddonsByStatus("archived"), - ]) - .then(() => { - if (!this.selectedAddonId) - this.selectedAddonId = (this.selectedItems[0] || {}).key || this.selectedAddonId; - }) - .catch((err) => { - this.$root.showSnackBar(err, "error"); - }); - }, - search_update(updateId = false) { - const params = this.search_load(); - this.status = params.get("status") || this.status; - this.$nextTick(() => { - this.selectedAddonId = params.get("id") || this.selectedAddonId; - }); - }, - }, - created() { - this.search_update(); - window.addEventListener( - "hashchange", - () => { - this.search_update(); - }, - false, - ); - }, - mounted() { - this.update(); - window.updatePageStyles(this); + axios + .put(`${this.$root.apiURL}/addons/${id}/review`, data, this.$root.apiOptions) + .then(() => { + this.$root.showSnackBar(this.$root.lang().global.ends_success, "success"); + this.selectedAddonId = undefined; + this.update(); + }) + .catch((err) => { + console.error(err); + this.$root.showSnackBar(err, "error"); + }); + }, + closeDenyPopup(send = false, reason) { + this.showDenyPopup = false; + if (send) this.reviewAddon(this.denyAddon, this.archive ? "archived" : "denied", reason); + }, + openDenyPopup(addon, archive = undefined) { + this.archive = !!archive; + this.showDenyPopup = true; + this.denyAddon = addon; + }, + getAddonsByStatus(status) { + return axios + .get(`${this.$root.apiURL}/addons/${status}`, this.$root.apiOptions) + .then((res) => { + this.addons[status] = res.data; + this.addons[status].forEach((addon) => (addon.options.tags = addon.options.tags.sort())); + this.loading[status] = false; + this.$forceUpdate(); + }) + .catch((err) => { + console.error(err); + this.$root.showSnackBar(err, "error"); + }); + }, + getContributors() { + axios + .get(`${this.$root.apiURL}/users/names`) + .then((res) => { + this.contributors = res.data; + }) + .catch((err) => { + console.error(err); + this.$root.showSnackBar(err, "error"); + }); + }, + update() { + Promise.all([ + this.getContributors(), + this.getAddonsByStatus("pending"), + this.getAddonsByStatus("denied"), + this.getAddonsByStatus("approved"), + this.getAddonsByStatus("archived"), + ]) + .then(() => { + if (!this.selectedAddonId) + this.selectedAddonId = (this.selectedItems[0] || {}).key || this.selectedAddonId; + }) + .catch((err) => { + this.$root.showSnackBar(err, "error"); + }); + }, + search_update(updateId = false) { + const params = this.search_load(); + this.status = params.get("status") || this.status; + this.$nextTick(() => { + this.selectedAddonId = params.get("id") || this.selectedAddonId; + }); + }, + }, + created() { + this.search_update(); + window.addEventListener( + "hashchange", + () => { + this.search_update(); + }, + false, + ); + }, + mounted() { + this.update(); + window.updatePageStyles(this); - this.$root.$on("openDenyPopup", (args) => { - this.openDenyPopup(...args); - }); + this.$root.$on("openDenyPopup", (args) => { + this.openDenyPopup(...args); + }); - this.$root.$on("reviewAddon", (args) => { - this.reviewAddon(...args); - }); - }, + this.$root.$on("reviewAddon", (args) => { + this.reviewAddon(...args); + }); + }, }; diff --git a/pages/review/review_categories.js b/pages/review/review_categories.js index 19b7946..6136d1d 100644 --- a/pages/review/review_categories.js +++ b/pages/review/review_categories.js @@ -1,20 +1,20 @@ export default { - name: "review-categories", - props: { - value: { - type: String, - required: true, - }, - categories: { - type: Array, // [{ label: String, color: String }] - required: true, - }, - activeColor: { - type: String, - required: true, - }, - }, - template: ` + name: "review-categories", + props: { + value: { + type: String, + required: true, + }, + categories: { + type: Array, // [{ label: String, color: String }] + required: true, + }, + activeColor: { + type: String, + required: true, + }, + }, + template: ` `, - data() { - return { - content: {}, - }; - }, - methods: { - onClick(val) { - this.$emit("input", val); - }, - }, - computed: { - classes() { - return this.categories.map((v) => - v.value === this.value ? this.activeColor + " selected" : "", - ); - }, - }, + data() { + return { + content: {}, + }; + }, + methods: { + onClick(val) { + this.$emit("input", val); + }, + }, + computed: { + classes() { + return this.categories.map((v) => + v.value === this.value ? this.activeColor + " selected" : "", + ); + }, + }, }; diff --git a/pages/review/review_list.js b/pages/review/review_list.js index 754654a..62aedc1 100644 --- a/pages/review/review_list.js +++ b/pages/review/review_list.js @@ -1,24 +1,24 @@ export default { - name: "review-list", - props: { - value: { - type: String, - required: true, - }, - items: { - type: Array, // [{ primary: String, secondary: String, key: Number }] - required: true, - }, - empty: { - type: String, - required: true, - }, - activeColor: { - type: String, - required: true, - }, - }, - template: ` + name: "review-list", + props: { + value: { + type: String, + required: true, + }, + items: { + type: Array, // [{ primary: String, secondary: String, key: Number }] + required: true, + }, + empty: { + type: String, + required: true, + }, + activeColor: { + type: String, + required: true, + }, + }, + template: ` `, - methods: { - onClick(key) { - this.$emit("input", key); - }, - }, - computed: { - classes() { - return this.items.map((v) => (v.key === this.value ? this.activeColor + " selected" : "")); - }, - }, + methods: { + onClick(key) { + this.$emit("input", key); + }, + }, + computed: { + classes() { + return this.items.map((v) => (v.key === this.value ? this.activeColor + " selected" : "")); + }, + }, }; diff --git a/pages/review/review_preview.js b/pages/review/review_preview.js index 493f370..35031a3 100644 --- a/pages/review/review_preview.js +++ b/pages/review/review_preview.js @@ -2,19 +2,19 @@ const FullscreenPreview = () => import("../addon/fullscreen-preview.js"); const ImagePreviewer = () => import("../addon/image-previewer.js"); export default { - name: "review-preview", - components: { - FullscreenPreview, - ImagePreviewer, - }, - props: { - addonId: { - type: String, - required: false, - default: undefined, - }, - }, - template: ` + name: "review-preview", + components: { + FullscreenPreview, + ImagePreviewer, + }, + props: { + addonId: { + type: String, + required: false, + default: undefined, + }, + }, + template: `
`, - data() { - return { - imagePreview: "", - dialogAddon: {}, - dialogOpen: false, + data() { + return { + imagePreview: "", + dialogAddon: {}, + dialogOpen: false, - addonInPanelLoading: true, - addonInPanel: {}, - addonURL: undefined, - addonInPanelHeaderURL: undefined, - contributors: [], - }; - }, - computed: { - addonSources() { - return (this.addonInPanel.files || []) - .filter((f) => f.use === "carousel" || f.use === "screenshot") - .map((f) => f.source); - }, - status() { - return this.addonInPanel && this.addonInPanel.approval - ? this.addonInPanel.approval.status - : undefined; - }, - }, - watch: { - addonId: { - handler(n) { - if (n === undefined) return; - this.getAddon(n); - }, - immediate: true, - }, - }, - methods: { - getAddon(id) { - this.addonInPanelLoading = true; + addonInPanelLoading: true, + addonInPanel: {}, + addonURL: undefined, + addonInPanelHeaderURL: undefined, + contributors: [], + }; + }, + computed: { + addonSources() { + return (this.addonInPanel.files || []) + .filter((f) => f.use === "carousel" || f.use === "screenshot") + .map((f) => f.source); + }, + status() { + return this.addonInPanel && this.addonInPanel.approval + ? this.addonInPanel.approval.status + : undefined; + }, + }, + watch: { + addonId: { + handler(n) { + if (n === undefined) return; + this.getAddon(n); + }, + immediate: true, + }, + }, + methods: { + getAddon(id) { + this.addonInPanelLoading = true; - // allSettled if no header res - Promise.allSettled([ - axios.get(`${this.$root.apiURL}/addons/${id}/all`, this.$root.apiOptions), - axios.get(`${this.$root.apiURL}/addons/${id}/files/header`, this.$root.apiOptions), - ]).then(([res, header_res]) => { - // void value if already here (closing tab) - if (this.addonInPanel.id === res.value.data.id) { - this.addonInPanel = {}; - this.addonInPanelLoading = true; - return; - } + // allSettled if no header res + Promise.allSettled([ + axios.get(`${this.$root.apiURL}/addons/${id}/all`, this.$root.apiOptions), + axios.get(`${this.$root.apiURL}/addons/${id}/files/header`, this.$root.apiOptions), + ]).then(([res, header_res]) => { + // void value if already here (closing tab) + if (this.addonInPanel.id === res.value.data.id) { + this.addonInPanel = {}; + this.addonInPanelLoading = true; + return; + } - this.addonInPanel = res.value.data; - this.addonInPanelLoading = false; + this.addonInPanel = res.value.data; + this.addonInPanelLoading = false; - if (header_res.value) - this.addonInPanelHeaderURL = header_res.value.data + "?t=" + new Date().getTime(); - else this.addonInPanelHeaderURL = null; - }); - }, - openDialog() { - this.dialogAddon = this.addonInPanel; - this.dialogOpen = true; - }, - closeDialog() { - this.dialogOpen = false; - this.dialogAddon = {}; - this.update(); - }, - getUsername(id) { - if (id === null || id === undefined) return "Herobrine"; - return this.contributors.filter((c) => c.id === id)[0].username || "Unknown User"; - }, - openDenyPopup(...args) { - this.$root.$emit("openDenyPopup", args); - }, - reviewAddon(...args) { - this.$root.$emit("reviewAddon", args); - }, - }, - created() { - axios - .get(`${this.$root.apiURL}/users/names`) - .then((res) => { - this.contributors = res.data; - }) - .catch((err) => { - console.error(err); - this.$root.showSnackBar(err, "error"); - }); - }, + if (header_res.value) + this.addonInPanelHeaderURL = header_res.value.data + "?t=" + new Date().getTime(); + else this.addonInPanelHeaderURL = null; + }); + }, + openDialog() { + this.dialogAddon = this.addonInPanel; + this.dialogOpen = true; + }, + closeDialog() { + this.dialogOpen = false; + this.dialogAddon = {}; + this.update(); + }, + getUsername(id) { + if (id === null || id === undefined) return "Herobrine"; + return this.contributors.filter((c) => c.id === id)[0].username || "Unknown User"; + }, + openDenyPopup(...args) { + this.$root.$emit("openDenyPopup", args); + }, + reviewAddon(...args) { + this.$root.$emit("reviewAddon", args); + }, + }, + created() { + axios + .get(`${this.$root.apiURL}/users/names`) + .then((res) => { + this.contributors = res.data; + }) + .catch((err) => { + console.error(err); + this.$root.showSnackBar(err, "error"); + }); + }, }; diff --git a/pages/review/review_previewer.js b/pages/review/review_previewer.js index ddebc65..f538b12 100644 --- a/pages/review/review_previewer.js +++ b/pages/review/review_previewer.js @@ -1,23 +1,23 @@ const ReviewPreview = () => import("./review_preview.js"); export default { - name: "review-previewer", - components: { - ReviewPreview, - }, - props: { - addonId: { - type: String, - required: false, - default: undefined, - }, - color: { - type: String, - required: false, - default: "black", - }, - }, - template: ` + name: "review-previewer", + components: { + ReviewPreview, + }, + props: { + addonId: { + type: String, + required: false, + default: undefined, + }, + color: { + type: String, + required: false, + default: "black", + }, + }, + template: `