From 02ee969a1cb2547355453d48f9a233206f3ce1df Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 03:21:23 +0000 Subject: [PATCH 01/10] =?UTF-8?q?=F0=9F=92=84=20add=20motion=20positioning?= =?UTF-8?q?=20helper=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scss/_fonts.scss | 7 ++++--- src/scss/_helpers.scss | 1 + src/scss/helpers/_motion.scss | 13 +++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/scss/helpers/_motion.scss diff --git a/src/scss/_fonts.scss b/src/scss/_fonts.scss index fd7bb6c14..86e2e7013 100644 --- a/src/scss/_fonts.scss +++ b/src/scss/_fonts.scss @@ -5,11 +5,12 @@ $monospace-fonts: 'SF Mono', 'Fira Code', 'Source Code Pro', - menlo, - monaco, + 'Cascadia Code', 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', - consolas, + 'menlo', + 'monaco', + 'consolas', monospace, sans-serif; diff --git a/src/scss/_helpers.scss b/src/scss/_helpers.scss index 17baa50a0..642718ed7 100644 --- a/src/scss/_helpers.scss +++ b/src/scss/_helpers.scss @@ -1,5 +1,6 @@ @import 'helpers/disabled'; @import 'helpers/hidden'; +@import 'helpers/motion'; @import 'helpers/pull-right'; @import 'helpers/text-collapse'; @import 'helpers/text-format'; diff --git a/src/scss/helpers/_motion.scss b/src/scss/helpers/_motion.scss new file mode 100644 index 000000000..387ff5d95 --- /dev/null +++ b/src/scss/helpers/_motion.scss @@ -0,0 +1,13 @@ +.presence-container { + position: relative; + + width: 100%; + height: 100%; + + .presence-animator { + position: absolute; + + width: 100%; + height: 100%; + } +} From ed8d2cee31d6ca23debe4aac1b79e20e78f682ef Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 03:21:54 +0000 Subject: [PATCH 02/10] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20add=20fetch=20to=20g?= =?UTF-8?q?lobals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 440b3c088..50a79771d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,6 +13,7 @@ module.exports = { ], globals: { $$BUILD: 'readonly', + fetch: 'readonly', }, rules: { 'jsx-a11y/no-noninteractive-element-interactions': ['off'], // We intend to enable this once we refactor certain key components. From 4675ec2fa714e64cf9b7ed281e288f4bc5bb67e2 Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 04:00:22 +0000 Subject: [PATCH 03/10] =?UTF-8?q?=E2=9C=A8=20(JsonApiContext):=20Add=20dat?= =?UTF-8?q?e=20property=20to=20req,=20add=20fetch=20helper=20to=20ctx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../middleware/jsonApiRoute/JsonApiContext.js | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/util/server/middleware/jsonApiRoute/JsonApiContext.js b/src/util/server/middleware/jsonApiRoute/JsonApiContext.js index bc24f6923..42748d779 100644 --- a/src/util/server/middleware/jsonApiRoute/JsonApiContext.js +++ b/src/util/server/middleware/jsonApiRoute/JsonApiContext.js @@ -1,3 +1,4 @@ +import axios from 'axios' import { NextApiRequest, NextApiResponse } from 'next' import bindMethod from '~/util/decorators/bindMethod' @@ -8,9 +9,15 @@ import getEnv from '~/util/server/getEnv' const env = getEnv() +const cacheStorage = {} function modRequest (req) { + // Add request time + Reflect.defineProperty(req, 'date', { + value: Date.now(), + }) + // Add IP handlers Reflect.defineProperty(req, 'ips', { get: () => { @@ -27,12 +34,14 @@ function modRequest (req) { }, }) + // Add fingerprint to top level Reflect.defineProperty(req, 'fingerprint', { get: () => { return req.headers['x-fingerprint'] }, }) + // Add getHeader helper Reflect.defineProperty(req, 'getHeader', { value: (headerName) => { return req.headers[headerName.toLowerCase()] @@ -147,6 +156,45 @@ export default class JsonApiContext { } } + @bindMethod + async fetch (options) { + const { cache, ...restOptions } = options + + if (cache) { + const storedData = cacheStorage[cache.key] + + if (storedData) { + if (this.req.date < storedData.last + cache.maxAge) { + return { ...storedData.data } + } + + delete cacheStorage[cache.key] + } + } + + try { + const response = await axios.request({ + method: 'get', + ...restOptions, + }) + + if (cache) { + cacheStorage[cache.key] = { + data: { ...response }, + last: this.req.date, + } + } + + return response + } catch ({ response }) { + throw new InternalServerAPIError(null, { + url: options.url, + status: response.status, + statusText: response.statusText, + }) + } + } + /** * @returns {any} */ From b4da43a871af03ad21971b8a5317a9811a5c506c Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 04:00:52 +0000 Subject: [PATCH 04/10] =?UTF-8?q?=E2=9C=A8=20(JsonEditor):=20add=20JSON=20?= =?UTF-8?q?editing=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/JsonEditor/JsonEditor.js | 47 +++++++++++++++++++ .../JsonEditor/JsonEditor.module.scss | 42 +++++++++++++++++ src/components/JsonEditor/JsonField.js | 43 +++++++++++++++++ src/components/JsonEditor/JsonObject.js | 47 +++++++++++++++++++ src/components/JsonEditor/index.js | 1 + 5 files changed, 180 insertions(+) create mode 100644 src/components/JsonEditor/JsonEditor.js create mode 100644 src/components/JsonEditor/JsonEditor.module.scss create mode 100644 src/components/JsonEditor/JsonField.js create mode 100644 src/components/JsonEditor/JsonObject.js create mode 100644 src/components/JsonEditor/index.js diff --git a/src/components/JsonEditor/JsonEditor.js b/src/components/JsonEditor/JsonEditor.js new file mode 100644 index 000000000..81ed06f01 --- /dev/null +++ b/src/components/JsonEditor/JsonEditor.js @@ -0,0 +1,47 @@ +import PropTypes from 'prop-types' + +import JsonObject from '~/components/JsonEditor/JsonObject' +import useForm from '~/hooks/useForm' + +import styles from './JsonEditor.module.scss' + + + + +// Component Constants + + + + + +function JsonEditor (props) { + const { + className, + data, + } = props + + const { Form, state } = useForm({ data }) + + return ( + <> +
+ + +
+
+        {JSON.stringify(state, null, '  ')}
+      
+ + ) +} + +JsonEditor.propTypes = { + className: PropTypes.string, + data: PropTypes.object.isRequired, +} + + + + + +export default JsonEditor diff --git a/src/components/JsonEditor/JsonEditor.module.scss b/src/components/JsonEditor/JsonEditor.module.scss new file mode 100644 index 000000000..f2cb0fadf --- /dev/null +++ b/src/components/JsonEditor/JsonEditor.module.scss @@ -0,0 +1,42 @@ +@import '../../scss/fonts'; +@import '../../scss/colors'; + +.jsonEditor { + padding: 0.5rem 1rem; + + border: 1px solid $black; + + background: rgba($grey, 0.25); +} + +.jsonObject { + padding: 0.5rem 0; +} + +.hasDepth { + padding-left: 2rem; +} + +.jsonField { + display: flex; + + flex-direction: row; + align-items: center; + + padding: 0.5rem 0 0.5rem 2rem; +} + +.fieldInput { + display: inline-block; +} + + +.fieldLabel { + display: inline-block; + position: relative; + + + padding-right: 0.75rem; + + font-family: $monospace-fonts; +} diff --git a/src/components/JsonEditor/JsonField.js b/src/components/JsonEditor/JsonField.js new file mode 100644 index 000000000..ad7cd4f81 --- /dev/null +++ b/src/components/JsonEditor/JsonField.js @@ -0,0 +1,43 @@ +import { useCallback } from 'react' + +import { useField } from '~/hooks/useForm' + +import styles from './JsonEditor.module.scss' + + + +export default function JsonField (props) { + const { + node, + name, + path, + depth, + } = props + const labelId = `${path}-label` + const inputId = `${path}-input` + + const { + value = '', + handleChange, + } = useField(path) + + const onChange = useCallback((event) => { + handleChange(event, event.target.value === '' ? node : undefined) + }, [handleChange, node]) + + return ( +
0 }]}> + + + +
+ ) +} diff --git a/src/components/JsonEditor/JsonObject.js b/src/components/JsonEditor/JsonObject.js new file mode 100644 index 000000000..6bb85c6c0 --- /dev/null +++ b/src/components/JsonEditor/JsonObject.js @@ -0,0 +1,47 @@ +import _isPlainObject from 'lodash/isPlainObject' + +import JsonField from '~/components/JsonEditor/JsonField' + +import styles from './JsonEditor.module.scss' + +function getComponent (value) { + if (_isPlainObject(value)) { + return JsonObject + } + + return JsonField +} + +export default function JsonObject (props) { + const { + node, + name, + depth = 0, + path, + hasNext, + } = props + const propDepth = depth + 1 + const nodeLength = Object.keys(node).length + return ( +
0 }]}> + {`${depth ? `"${name}": ` : ''}{`} + { + Object.entries(node).map(([propName, propNode], idx) => { + const Component = getComponent(propNode) + const propPath = depth ? `${path}.${propName}` : propName + + return ( + + ) + }) + } + {`}${hasNext ? ',' : ''}`} +
+ ) +} diff --git a/src/components/JsonEditor/index.js b/src/components/JsonEditor/index.js new file mode 100644 index 000000000..bb29dfa76 --- /dev/null +++ b/src/components/JsonEditor/index.js @@ -0,0 +1 @@ +export { default } from './JsonEditor' From e40741c98d3d9df46c9d8ac84833f10e6d748ebd Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 04:33:58 +0000 Subject: [PATCH 05/10] =?UTF-8?q?=E2=9C=A8=20add=20404=20not=20found=20API?= =?UTF-8?q?=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/data/apiErrorLocalisations.js | 5 +++++ src/util/server/errors.js | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/data/apiErrorLocalisations.js b/src/data/apiErrorLocalisations.js index a402dd2d8..c1d5bf5fd 100644 --- a/src/data/apiErrorLocalisations.js +++ b/src/data/apiErrorLocalisations.js @@ -33,6 +33,11 @@ const apiErrorLocalisations = { title: 'Internal Server Error', detail: 'The server encountered an unexpected condition that prevented it from fulfilling the request.', }, + + not_found: { + title: 'Not Found', + detail: 'The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.', + }, } export default apiErrorLocalisations diff --git a/src/util/server/errors.js b/src/util/server/errors.js index 29009bbe8..ca1337104 100644 --- a/src/util/server/errors.js +++ b/src/util/server/errors.js @@ -101,3 +101,9 @@ export class NotImplementedAPIError extends APIError { return 'not_implemented' } } + +export class NotFoundAPIError extends APIError { + get status () { + return 'not_found' + } +} From a17b586e4291b32872fd1b448c5d2673bbce9164 Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 05:19:28 +0000 Subject: [PATCH 06/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20use=20new=20built-in?= =?UTF-8?q?=20caching=20to=20simplify=20queue=20info=20gathering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/api/qms/queue.js | 125 ++++++++++++++----------------------- 1 file changed, 48 insertions(+), 77 deletions(-) diff --git a/src/pages/api/qms/queue.js b/src/pages/api/qms/queue.js index f39308d47..8106b8b2d 100644 --- a/src/pages/api/qms/queue.js +++ b/src/pages/api/qms/queue.js @@ -1,6 +1,3 @@ -import { HttpStatus } from '@fuelrats/web-util/http' -import axios from 'axios' - import { InternalServerAPIError } from '~/util/server/errors' import getEnv from '~/util/server/getEnv' import acceptMethod from '~/util/server/middleware/acceptMethod' @@ -10,92 +7,66 @@ import jsonApiRoute from '~/util/server/middleware/jsonApiRoute' // Module constants -const cache = { - count: { - maxAge: 10000, - lastCheck: 0, - value: 0, - }, - max: { - maxAge: 60000, - lastCheck: 0, - value: 0, - }, -} const env = getEnv() +function getQueueLength (ctx) { + return ctx.fetch({ + url: `${env.qms.url}/api/v1/queue/`, + headers: { + Authorization: `Bearer ${env.qms.token}`, + }, + cache: { + key: 'qms-queue-count', + maxAge: 10000, + }, + }).then( + ({ data }) => { + return data.filter((rescue) => { + return !rescue.pending && !rescue.in_progress + }).length + }, + ({ response }) => { + ctx.error(new InternalServerAPIError(null, { + internalError: { + url: 'qms/api/v1/queue/', + status: response.status, + statusText: response.statusText, + }, + })) + }, + ) +} +function getMaxQueueLength (ctx) { + return ctx.fetch({ + url: `${env.qms.url}/api/v1/config/`, + headers: { + Authorization: `Bearer ${env.qms.token}`, + }, + cache: { + key: 'qms-queue-max-count', + maxAge: 60000, + }, + }).then( + ({ data }) => { + return data.max_active_clients + }, + (error) => { + ctx.error(error) + }, + ) +} export default jsonApiRoute( acceptMethod.GET(), async (ctx) => { - const nowTime = Date.now() - - if (nowTime - cache.count.lastCheck >= cache.count.maxAge) { - cache.count.lastCheck = nowTime - - const { data, status, statusText } = await axios.get( - `${env.qms.url}/api/v1/queue/`, - { - headers: { - Authorization: `Bearer ${env.qms.token}`, - }, - }, - ) - - if (status === HttpStatus.OK) { - const rescueQueue = data.filter((rescue) => { - return !rescue.pending && !rescue.in_progress - }) - - cache.count.value = rescueQueue.length - } else { - ctx.error(new InternalServerAPIError(null, { - internalError: { - url: '/api/v1/queue/', - status, - statusText, - }, - })) - } - } - - if (nowTime - cache.max.lastCheck >= cache.max.maxAge) { - cache.max.lastCheck = nowTime - const { data, status, statusText } = await axios.get( - `${env.qms.url}/api/v1/config/`, - { - headers: { - Authorization: `Bearer ${env.qms.token}`, - }, - }, - ) - - if (status === HttpStatus.OK) { - cache.max.value = data.max_active_clients - } else { - ctx.error(new InternalServerAPIError(null, { - internalError: { - url: '/api/v1/config/', - status, - statusText, - }, - })) - } - } - - ctx.send({ id: 'static', type: 'queue-statistics', attributes: { - queueLength: cache.count.value, - maxClients: cache.max.value, - }, - meta: { - queueAge: nowTime - cache.count.lastCheck, - maxClientAge: nowTime - cache.max.lastCheck, + queueLength: await getQueueLength(ctx), + maxClients: await getMaxQueueLength(ctx), }, }) }, From 583488cec6bba5f21c5640f17cf03d585d01cbc4 Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 05:19:52 +0000 Subject: [PATCH 07/10] =?UTF-8?q?=E2=9C=A8=20Add=20locale=20info=20gatheri?= =?UTF-8?q?ng=20routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/api/qms/locales/[id].js | 38 ++++++++++++++++++++++++++++++ src/pages/api/qms/locales/index.js | 32 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/pages/api/qms/locales/[id].js create mode 100644 src/pages/api/qms/locales/index.js diff --git a/src/pages/api/qms/locales/[id].js b/src/pages/api/qms/locales/[id].js new file mode 100644 index 000000000..d78419c69 --- /dev/null +++ b/src/pages/api/qms/locales/[id].js @@ -0,0 +1,38 @@ +import { listLocaleFiles } from '~/pages/api/qms/locales' +import { NotFoundAPIError } from '~/util/server/errors' +import acceptMethod from '~/util/server/middleware/acceptMethod' +import jsonApiRoute from '~/util/server/middleware/jsonApiRoute' + +const maxAge = 21600000 // 6 hours + +export function getLocaleData (ctx, localeMeta) { + return ctx.fetch({ + url: localeMeta.links.related, + cache: { + key: `qms-locale-data-${localeMeta.id}`, + maxAge, + }, + }).then(({ data }) => { + return data + }) +} + +export default jsonApiRoute( + acceptMethod.GET(), + async (ctx) => { + const localeMeta = (await listLocaleFiles(ctx)).find((data) => { + return data.id === ctx.req.query.id + }) + if (!localeMeta) { + throw new NotFoundAPIError({ parameter: 'id' }) + } + + ctx.send({ + id: localeMeta.id, + type: 'locale-data', + attributes: { + data: await getLocaleData(ctx, localeMeta), + }, + }) + }, +) diff --git a/src/pages/api/qms/locales/index.js b/src/pages/api/qms/locales/index.js new file mode 100644 index 000000000..d984728d9 --- /dev/null +++ b/src/pages/api/qms/locales/index.js @@ -0,0 +1,32 @@ +import acceptMethod from '~/util/server/middleware/acceptMethod' +import jsonApiRoute from '~/util/server/middleware/jsonApiRoute' + +const maxAge = 21600000 // 6 hours + +export function listLocaleFiles (ctx) { + return ctx.fetch({ + url: 'https://api.github.com/repos/fuelrats/QMS/contents/qms/frontend/public/locales/en?ref=main', + cache: { + key: 'qms-locale-list', + maxAge, + }, + }).then(({ data }) => { + return data.map((fileMeta) => { + return { + id: fileMeta.name, + type: 'locale-files', + links: { + self: fileMeta.url, + related: fileMeta.download_url, + }, + } + }) + }) +} + +export default jsonApiRoute( + acceptMethod.GET(), + async (ctx) => { + ctx.send(await listLocaleFiles(ctx)) + }, +) From 72ff9db0618dd9c14cca0b0a3e1a64c0350aa504 Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 05:20:09 +0000 Subject: [PATCH 08/10] =?UTF-8?q?=E2=9C=A8=20Add=20page=20to=20edit=20QMS?= =?UTF-8?q?=20locale=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/locale-editor.js | 122 +++++++++++++++++++++++ src/scss/pages/locale-editor.module.scss | 3 + 2 files changed, 125 insertions(+) create mode 100644 src/pages/locale-editor.js create mode 100644 src/scss/pages/locale-editor.module.scss diff --git a/src/pages/locale-editor.js b/src/pages/locale-editor.js new file mode 100644 index 000000000..e03e3f3d8 --- /dev/null +++ b/src/pages/locale-editor.js @@ -0,0 +1,122 @@ +import axios from 'axios' +import { AnimatePresence, m } from 'framer-motion' +import getConfig from 'next/config' +import { useCallback, useEffect, useRef, useState } from 'react' + +import JsonEditor from '~/components/JsonEditor' +import styles from '~/scss/pages/locale-editor.module.scss' + +const { publicRuntimeConfig } = getConfig() +const { appUrl } = publicRuntimeConfig + +/* eslint-disable id-length */ +const formMotionConfig = { + initial: { y: '-400px', opacity: 0 }, + animate: { y: 0, opacity: 1 }, + exit: { y: '400px', opacity: 0 }, + transition: { + type: 'spring', + stiffness: 400, + damping: 25, + restDelta: 0.5, + restSpeed: 10, + }, +} +/* eslint-enable id-length */ + + +function LocaleEditor ({ locales }) { + const [localeLoading, setLocaleLoading] = useState(false) + const [localeData, setLocaleData] = useState({}) + const localeDataRef = useRef(localeData) + localeDataRef.current = localeData + const [activeLocale, setActiveLocale] = useState(locales[0].id) + + const onSelectChange = useCallback((event) => { + setActiveLocale(event.target.value) + }, []) + + useEffect(() => { + async function getLocaleData () { + if (activeLocale && !localeDataRef.current[activeLocale]) { + setLocaleLoading(true) + const { data } = await axios.get(`/api/qms/locales/${activeLocale}`) + + setLocaleData((curData) => { + return { + ...curData, + [activeLocale]: data.data.attributes.data, + } + }) + setLocaleLoading(false) + } + } + getLocaleData() + }, [activeLocale]) + + return ( +
+

+ { + `This page is a basic editing helper for the QMS locale files. They are automatically generated from english Locale files found on the main branch. + You may use basic HTML and some accept parameters ` + } + {'{{in_brackets_like_this}}'} + {'. Be sure to double check the default values for HTML content and parameterized strings!'} +

+
+ + +
+
+ {'GitHub'} + {' | '} + {'Raw'} + {' | '} + {'Metadata'} +
+
+
+ + { + Boolean(localeData[activeLocale]) && ( + + + + ) + } + +
+
+ ) +} + +LocaleEditor.getInitialProps = async () => { + const { data } = await axios.get(`${appUrl}/api/qms/locales`) + + return { + locales: data.data, + } +} + +LocaleEditor.getPageMeta = () => { + return { + title: 'Locale Editor', + } +} + + + + +export default LocaleEditor diff --git a/src/scss/pages/locale-editor.module.scss b/src/scss/pages/locale-editor.module.scss new file mode 100644 index 000000000..dbb1b7199 --- /dev/null +++ b/src/scss/pages/locale-editor.module.scss @@ -0,0 +1,3 @@ +.explainer { + max-width: 90rem; +} From 3de4e43aaa87d151587182d5e89a5aebbd9100b6 Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 05:22:04 +0000 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=92=AC=20(locale-editor-page):=20up?= =?UTF-8?q?date=20explainer=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/locale-editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/locale-editor.js b/src/pages/locale-editor.js index e03e3f3d8..3bd3f8322 100644 --- a/src/pages/locale-editor.js +++ b/src/pages/locale-editor.js @@ -62,7 +62,7 @@ function LocaleEditor ({ locales }) { You may use basic HTML and some accept parameters ` } {'{{in_brackets_like_this}}'} - {'. Be sure to double check the default values for HTML content and parameterized strings!'} + {'. Be sure to double check the default values for HTML content and parameterized strings! Is this overkill? absolutely. Did we do it anyway? Of course!'}

From c1b92f8ba6bd775a8fa993f1dc26f327483239a4 Mon Sep 17 00:00:00 2001 From: Cameron Welter Date: Sun, 5 Sep 2021 05:33:09 +0000 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=93=9D=20(CHANGELOG.md):=20add=20ch?= =?UTF-8?q?anges=20for=20#370?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f29936196..872aae3ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ For detailed rules of this file, see [Changelog Rules](#changelog-rules) ## [Unreleased][] ### ✨ Added -* +* A new page to assist with editing QMS locale files has been added. - [#370][] +* A general purpose JSON object editor has been implemented. this should come in handy at some point! - [#370][] ### ⚡ Changed * Error readout for unknown API Errors has been improved. - [#328][] @@ -21,7 +22,7 @@ For detailed rules of this file, see [Changelog Rules](#changelog-rules) * Moved all custom server functions into suitable replacements provided by our site framework. - [#329][] * This move has let us drop our entire custom backend. * Updated `` with an improved loading state animation. - [#330][] -* Other smaller changes to sreamline development. - [#328][], [#329][] +* Other smaller changes to sreamline development. - [#328][], [#329][], [#370][] ### 🐛 Fixed @@ -39,6 +40,7 @@ For detailed rules of this file, see [Changelog Rules](#changelog-rules) [#329]: https://github.com/FuelRats/fuelrats.com/pull/329 [#330]: https://github.com/FuelRats/fuelrats.com/pull/330 [#333]: https://github.com/FuelRats/fuelrats.com/pull/333 +[#370]: https://github.com/FuelRats/fuelrats.com/pull/370 [Unreleased]: https://github.com/FuelRats/fuelrats.com/compare/v2.13.0...HEAD