- day.get('uses'))
- .toArray()}
- >
+ 0)}>
@@ -94,7 +89,13 @@ const Hashtag = ({ hashtag }) => (
);
Hashtag.propTypes = {
- hashtag: ImmutablePropTypes.map.isRequired,
+ name: PropTypes.string,
+ href: PropTypes.string,
+ to: PropTypes.string,
+ people: PropTypes.number,
+ uses: PropTypes.number,
+ history: PropTypes.arrayOf(PropTypes.number),
+ className: PropTypes.string,
};
export default Hashtag;
diff --git a/app/javascript/flavours/glitch/components/skeleton.js b/app/javascript/flavours/glitch/components/skeleton.js
new file mode 100644
index 00000000000..09093e99c75
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/skeleton.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const Skeleton = ({ width, height }) =>
;
+
+Skeleton.propTypes = {
+ width: PropTypes.number,
+ height: PropTypes.number,
+};
+
+export default Skeleton;
diff --git a/app/javascript/flavours/glitch/containers/admin_component.js b/app/javascript/flavours/glitch/containers/admin_component.js
new file mode 100644
index 00000000000..64dabac8ba9
--- /dev/null
+++ b/app/javascript/flavours/glitch/containers/admin_component.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { IntlProvider, addLocaleData } from 'react-intl';
+import { getLocale } from 'mastodon/locales';
+
+const { localeData, messages } = getLocale();
+addLocaleData(localeData);
+
+export default class AdminComponent extends React.PureComponent {
+
+ static propTypes = {
+ locale: PropTypes.string.isRequired,
+ children: PropTypes.node.isRequired,
+ };
+
+ render () {
+ const { locale, children } = this.props;
+
+ return (
+
+ {children}
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/containers/media_container.js b/app/javascript/flavours/glitch/containers/media_container.js
index 8657b8064af..1ddbc706b3b 100644
--- a/app/javascript/flavours/glitch/containers/media_container.js
+++ b/app/javascript/flavours/glitch/containers/media_container.js
@@ -7,7 +7,7 @@ import { getLocale } from 'mastodon/locales';
import { getScrollbarWidth } from 'flavours/glitch/util/scrollbar';
import MediaGallery from 'flavours/glitch/components/media_gallery';
import Poll from 'flavours/glitch/components/poll';
-import Hashtag from 'flavours/glitch/components/hashtag';
+import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
import ModalRoot from 'flavours/glitch/components/modal_root';
import MediaModal from 'flavours/glitch/features/ui/components/media_modal';
import Video from 'flavours/glitch/features/video';
diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js
index 9a76e541811..cbc1f35e52f 100644
--- a/app/javascript/flavours/glitch/features/compose/components/search_results.js
+++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js
@@ -5,7 +5,7 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import AccountContainer from 'flavours/glitch/containers/account_container';
import StatusContainer from 'flavours/glitch/containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import Hashtag from 'flavours/glitch/components/hashtag';
+import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
import Icon from 'flavours/glitch/components/icon';
import { searchEnabled } from 'flavours/glitch/util/initial_state';
import LoadMore from 'flavours/glitch/components/load_more';
diff --git a/app/javascript/flavours/glitch/features/getting_started/components/trends.js b/app/javascript/flavours/glitch/features/getting_started/components/trends.js
index 0734ec72b96..ce4d94c647c 100644
--- a/app/javascript/flavours/glitch/features/getting_started/components/trends.js
+++ b/app/javascript/flavours/glitch/features/getting_started/components/trends.js
@@ -2,7 +2,7 @@ import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import Hashtag from 'flavours/glitch/components/hashtag';
+import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
import { FormattedMessage } from 'react-intl';
export default class Trends extends ImmutablePureComponent {
diff --git a/app/javascript/flavours/glitch/packs/admin.js b/app/javascript/flavours/glitch/packs/admin.js
new file mode 100644
index 00000000000..4c09ddb05c8
--- /dev/null
+++ b/app/javascript/flavours/glitch/packs/admin.js
@@ -0,0 +1,24 @@
+import 'packs/public-path';
+import ready from 'flavours/glitch/util/ready';
+
+ready(() => {
+ const React = require('react');
+ const ReactDOM = require('react-dom');
+
+ [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => {
+ const componentName = element.getAttribute('data-admin-component');
+ const { locale, ...componentProps } = JSON.parse(element.getAttribute('data-props'));
+
+ import('flavours/glitch/containers/admin_component').then(({ default: AdminComponent }) => {
+ return import('flavours/glitch/components/admin/' + componentName).then(({ default: Component }) => {
+ ReactDOM.render((
+
+
+
+ ), element);
+ });
+ }).catch(error => {
+ console.error(error);
+ });
+ });
+});
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index 4801a464401..24618c29f2a 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -1,3 +1,5 @@
+@use "sass:math";
+
$no-columns-breakpoint: 600px;
$sidebar-width: 240px;
$content-width: 840px;
@@ -925,10 +927,197 @@ a.name-tag,
}
}
+.dashboard__counters.admin-account-counters {
+ margin-top: 10px;
+}
+
.account-badges {
margin: -2px 0;
}
-.dashboard__counters.admin-account-counters {
- margin-top: 10px;
+.retention {
+ &__table {
+ &__number {
+ color: $secondary-text-color;
+ padding: 10px;
+ }
+
+ &__date {
+ white-space: nowrap;
+ padding: 10px 0;
+ text-align: left;
+ min-width: 120px;
+
+ &.retention__table__average {
+ font-weight: 700;
+ }
+ }
+
+ &__size {
+ text-align: center;
+ padding: 10px;
+ }
+
+ &__label {
+ font-weight: 700;
+ color: $darker-text-color;
+ }
+
+ &__box {
+ box-sizing: border-box;
+ background: $ui-highlight-color;
+ padding: 10px;
+ font-weight: 500;
+ color: $primary-text-color;
+ width: 52px;
+ margin: 1px;
+
+ @for $i from 0 through 10 {
+ &--#{10 * $i} {
+ background-color: rgba($ui-highlight-color, 1 * (math.div(max(1, $i), 10)));
+ }
+ }
+ }
+ }
+}
+
+.sparkline {
+ display: block;
+ text-decoration: none;
+ background: lighten($ui-base-color, 4%);
+ border-radius: 4px;
+ padding: 0;
+ position: relative;
+ padding-bottom: 55px + 20px;
+ overflow: hidden;
+
+ &__value {
+ display: flex;
+ line-height: 33px;
+ align-items: flex-end;
+ padding: 20px;
+ padding-bottom: 10px;
+
+ &__total {
+ display: block;
+ margin-right: 10px;
+ font-weight: 500;
+ font-size: 28px;
+ color: $primary-text-color;
+ }
+
+ &__change {
+ display: block;
+ font-weight: 500;
+ font-size: 18px;
+ color: $darker-text-color;
+ margin-bottom: -3px;
+
+ &.positive {
+ color: $valid-value-color;
+ }
+
+ &.negative {
+ color: $error-value-color;
+ }
+ }
+ }
+
+ &__label {
+ padding: 0 20px;
+ padding-bottom: 10px;
+ text-transform: uppercase;
+ color: $darker-text-color;
+ font-weight: 500;
+ }
+
+ &__graph {
+ position: absolute;
+ bottom: 0;
+
+ svg {
+ display: block;
+ margin: 0;
+ }
+
+ path:first-child {
+ fill: rgba($highlight-text-color, 0.25) !important;
+ fill-opacity: 1 !important;
+ }
+
+ path:last-child {
+ stroke: lighten($highlight-text-color, 6%) !important;
+ fill: none !important;
+ }
+ }
+}
+
+a.sparkline {
+ &:hover,
+ &:focus,
+ &:active {
+ background: lighten($ui-base-color, 6%);
+ }
+}
+
+.skeleton {
+ background-color: lighten($ui-base-color, 8%);
+ background-image: linear-gradient(90deg, lighten($ui-base-color, 8%), lighten($ui-base-color, 12%), lighten($ui-base-color, 8%));
+ background-size: 200px 100%;
+ background-repeat: no-repeat;
+ border-radius: 4px;
+ display: inline-block;
+ line-height: 1;
+ width: 100%;
+ animation: skeleton 1.2s ease-in-out infinite;
+}
+
+@keyframes skeleton {
+ 0% {
+ background-position: -200px 0;
+ }
+
+ 100% {
+ background-position: calc(200px + 100%) 0;
+ }
+}
+
+.dimension {
+ table {
+ width: 100%;
+ }
+
+ &__item {
+ border-bottom: 1px solid lighten($ui-base-color, 4%);
+
+ &__key {
+ font-weight: 500;
+ padding: 11px 10px;
+ }
+
+ &__value {
+ text-align: right;
+ color: $darker-text-color;
+ padding: 11px 10px;
+ }
+
+ &__indicator {
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: $ui-highlight-color;
+ margin-right: 10px;
+
+ @for $i from 0 through 10 {
+ &--#{10 * $i} {
+ background-color: rgba($ui-highlight-color, 1 * (math.div(max(1, $i), 10)));
+ }
+ }
+ }
+
+ &:last-child {
+ border-bottom: 0;
+ }
+ }
}
diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss
index 929769130e9..f7415368b82 100644
--- a/app/javascript/flavours/glitch/styles/components/search.scss
+++ b/app/javascript/flavours/glitch/styles/components/search.scss
@@ -171,7 +171,6 @@
&__current {
flex: 0 0 auto;
font-size: 24px;
- line-height: 36px;
font-weight: 500;
text-align: right;
padding-right: 15px;
@@ -193,5 +192,57 @@
fill: none !important;
}
}
+
+ &--requires-review {
+ .trends__item__name {
+ color: $gold-star;
+
+ a {
+ color: $gold-star;
+ }
+ }
+
+ .trends__item__current {
+ color: $gold-star;
+ }
+
+ .trends__item__sparkline {
+ path:first-child {
+ fill: rgba($gold-star, 0.25) !important;
+ }
+
+ path:last-child {
+ stroke: lighten($gold-star, 6%) !important;
+ }
+ }
+ }
+
+ &--disabled {
+ .trends__item__name {
+ color: lighten($ui-base-color, 12%);
+
+ a {
+ color: lighten($ui-base-color, 12%);
+ }
+ }
+
+ .trends__item__current {
+ color: lighten($ui-base-color, 12%);
+ }
+
+ .trends__item__sparkline {
+ path:first-child {
+ fill: rgba(lighten($ui-base-color, 12%), 0.25) !important;
+ }
+
+ path:last-child {
+ stroke: lighten(lighten($ui-base-color, 12%), 6%) !important;
+ }
+ }
+ }
+ }
+
+ &--compact &__item {
+ padding: 10px;
}
}
diff --git a/app/javascript/flavours/glitch/styles/dashboard.scss b/app/javascript/flavours/glitch/styles/dashboard.scss
index c0944d417dd..cad5a105be7 100644
--- a/app/javascript/flavours/glitch/styles/dashboard.scss
+++ b/app/javascript/flavours/glitch/styles/dashboard.scss
@@ -56,23 +56,56 @@
}
}
-.dashboard__widgets {
- display: flex;
- flex-wrap: wrap;
- margin: 0 -5px;
+.dashboard {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
+ grid-gap: 10px;
- & > div {
- flex: 0 0 33.333%;
- margin-bottom: 20px;
+ &__item {
+ &--span-double-column {
+ grid-column: span 2;
+ }
- & > div {
- padding: 0 5px;
+ &--span-double-row {
+ grid-row: span 2;
+ }
+
+ h4 {
+ padding-top: 20px;
}
}
- a:not(.name-tag) {
- color: $ui-secondary-color;
- font-weight: 500;
+ &__quick-access {
+ display: flex;
+ align-items: baseline;
+ border-radius: 4px;
+ background: $ui-highlight-color;
+ color: $primary-text-color;
+ transition: all 100ms ease-in;
+ font-size: 14px;
+ padding: 0 16px;
+ line-height: 36px;
+ height: 36px;
text-decoration: none;
+ margin-bottom: 4px;
+
+ &:active,
+ &:focus,
+ &:hover {
+ background-color: lighten($ui-highlight-color, 10%);
+ transition: all 200ms ease-out;
+ }
+
+ span {
+ flex: 1 1 auto;
+ }
+
+ .fa {
+ flex: 0 0 auto;
+ }
+
+ strong {
+ font-weight: 700;
+ }
}
}
diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml
index 2a98e4c29b8..ee2b699b2d4 100644
--- a/app/javascript/flavours/glitch/theme.yml
+++ b/app/javascript/flavours/glitch/theme.yml
@@ -1,7 +1,7 @@
# (REQUIRED) The location of the pack files.
pack:
about: packs/about.js
- admin: packs/public.js
+ admin: packs/admin.js
auth: packs/public.js
common:
filename: packs/common.js
diff --git a/app/javascript/flavours/glitch/util/numbers.js b/app/javascript/flavours/glitch/util/numbers.js
index 6f2505cae87..6ef563ad8f9 100644
--- a/app/javascript/flavours/glitch/util/numbers.js
+++ b/app/javascript/flavours/glitch/util/numbers.js
@@ -69,3 +69,11 @@ export function pluralReady(sourceNumber, division) {
return Math.trunc(sourceNumber / closestScale) * closestScale;
}
+
+/**
+ * @param {number} num
+ * @returns {number}
+ */
+export function roundTo10(num) {
+ return Math.round(num * 0.1) / 0.1;
+}
diff --git a/app/javascript/flavours/vanilla/theme.yml b/app/javascript/flavours/vanilla/theme.yml
index 74e9fb1b5f3..3263fd7d4fe 100644
--- a/app/javascript/flavours/vanilla/theme.yml
+++ b/app/javascript/flavours/vanilla/theme.yml
@@ -1,7 +1,7 @@
# (REQUIRED) The location of the pack files inside `pack_directory`.
pack:
about: about.js
- admin: public.js
+ admin: admin.js
auth: public.js
common:
filename: common.js
diff --git a/app/javascript/packs/admin.js b/app/javascript/packs/admin.js
new file mode 100644
index 00000000000..59901500011
--- /dev/null
+++ b/app/javascript/packs/admin.js
@@ -0,0 +1,24 @@
+import './public-path';
+import ready from '../mastodon/ready';
+
+ready(() => {
+ const React = require('react');
+ const ReactDOM = require('react-dom');
+
+ [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => {
+ const componentName = element.getAttribute('data-admin-component');
+ const { locale, ...componentProps } = JSON.parse(element.getAttribute('data-props'));
+
+ import('../mastodon/containers/admin_component').then(({ default: AdminComponent }) => {
+ return import('../mastodon/components/admin/' + componentName).then(({ default: Component }) => {
+ ReactDOM.render((
+
+
+
+ ), element);
+ });
+ }).catch(error => {
+ console.error(error);
+ });
+ });
+});