sforkowany z mirror/soapbox
Porównaj commity
4 Commity
Autor | SHA1 | Data |
---|---|---|
![]() |
829bbc7f48 | |
![]() |
ef86b27435 | |
![]() |
b84e4403ab | |
![]() |
7ff488bcf3 |
|
@ -0,0 +1,60 @@
|
||||||
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { updateSoapboxConfig } from 'soapbox/actions/admin';
|
||||||
|
import { getHost } from 'soapbox/actions/instance';
|
||||||
|
import { fetchSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
import toast from 'soapbox/toast';
|
||||||
|
|
||||||
|
import FeaturesPanel from '../features-panel';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
saved: { id: 'features_editor.saved', defaultMessage: 'Features updated!' },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IFeaturesEditor {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Admin feature editor. */
|
||||||
|
const FeaturesEditor: React.FC<IFeaturesEditor> = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const host = useAppSelector(state => getHost(state));
|
||||||
|
const rawConfig = useAppSelector(state => state.soapbox);
|
||||||
|
|
||||||
|
const userFeatures: Record<string, boolean> = useAppSelector(state => {
|
||||||
|
return (state.soapbox.get('features') || ImmutableMap()).toJS();
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateFeatures = async (features: Record<string, boolean>) => {
|
||||||
|
const params = rawConfig.set('features', features).toJS();
|
||||||
|
await dispatch(updateSoapboxConfig(params));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = async(features: Record<string, boolean>) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await dispatch(fetchSoapboxConfig(host));
|
||||||
|
await updateFeatures(features);
|
||||||
|
toast.success(intl.formatMessage(messages.saved));
|
||||||
|
setSubmitting(false);
|
||||||
|
} catch (e) {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FeaturesPanel
|
||||||
|
features={userFeatures}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={submitting}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FeaturesEditor;
|
|
@ -0,0 +1,56 @@
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
|
import { Column, Toggle } from 'soapbox/components/ui';
|
||||||
|
import { useFeatures } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: { id: 'features.title', defaultMessage: 'Features' },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IFeaturesPanel {
|
||||||
|
features: Record<string, boolean>,
|
||||||
|
onChange?: (features: Record<string, boolean>) => void,
|
||||||
|
disabled?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A UI for managing conditional feature flags. */
|
||||||
|
const FeaturesPanel: React.FC<IFeaturesPanel> = ({
|
||||||
|
features: userFeatures,
|
||||||
|
onChange,
|
||||||
|
disabled = false,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const autoFeatures = useFeatures();
|
||||||
|
|
||||||
|
const features = useMemo(() => {
|
||||||
|
return Object.assign({ ...autoFeatures }, { ...userFeatures });
|
||||||
|
}, [userFeatures, autoFeatures]);
|
||||||
|
|
||||||
|
const handleChange = (key: string): React.ChangeEventHandler<HTMLInputElement> => {
|
||||||
|
return (e) => {
|
||||||
|
if (onChange) {
|
||||||
|
onChange({ ...userFeatures, [key]: e.target.checked });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column label={intl.formatMessage(messages.title)}>
|
||||||
|
<List>
|
||||||
|
{Object.keys(autoFeatures).map(key => (
|
||||||
|
<ListItem label={key}>
|
||||||
|
<Toggle
|
||||||
|
checked={features[key]}
|
||||||
|
onChange={handleChange(key)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FeaturesPanel;
|
|
@ -109,6 +109,7 @@ import {
|
||||||
ThemeEditor,
|
ThemeEditor,
|
||||||
Quotes,
|
Quotes,
|
||||||
ServiceWorkerInfo,
|
ServiceWorkerInfo,
|
||||||
|
FeaturesEditor,
|
||||||
EventInformation,
|
EventInformation,
|
||||||
EventDiscussion,
|
EventDiscussion,
|
||||||
Events,
|
Events,
|
||||||
|
@ -296,7 +297,8 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||||
<WrappedRoute path='/soapbox/admin/reports' staffOnly page={AdminPage} component={Dashboard} content={children} exact />
|
<WrappedRoute path='/soapbox/admin/reports' staffOnly page={AdminPage} component={Dashboard} content={children} exact />
|
||||||
<WrappedRoute path='/soapbox/admin/log' staffOnly page={AdminPage} component={ModerationLog} content={children} exact />
|
<WrappedRoute path='/soapbox/admin/log' staffOnly page={AdminPage} component={ModerationLog} content={children} exact />
|
||||||
<WrappedRoute path='/soapbox/admin/users' staffOnly page={AdminPage} component={UserIndex} content={children} exact />
|
<WrappedRoute path='/soapbox/admin/users' staffOnly page={AdminPage} component={UserIndex} content={children} exact />
|
||||||
<WrappedRoute path='/soapbox/admin/theme' staffOnly page={AdminPage} component={ThemeEditor} content={children} exact />
|
<WrappedRoute path='/soapbox/admin/theme' adminOnly page={AdminPage} component={ThemeEditor} content={children} exact />
|
||||||
|
<WrappedRoute path='/soapbox/admin/features' adminOnly page={AdminPage} component={FeaturesEditor} content={children} />
|
||||||
<WrappedRoute path='/info' page={EmptyPage} component={ServerInfo} content={children} />
|
<WrappedRoute path='/info' page={EmptyPage} component={ServerInfo} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/developers/apps/create' developerOnly page={DefaultPage} component={CreateApp} content={children} />
|
<WrappedRoute path='/developers/apps/create' developerOnly page={DefaultPage} component={CreateApp} content={children} />
|
||||||
|
|
|
@ -310,6 +310,10 @@ export function ModerationLog() {
|
||||||
return import(/* webpackChunkName: "features/admin/moderation_log" */'../../admin/moderation-log');
|
return import(/* webpackChunkName: "features/admin/moderation_log" */'../../admin/moderation-log');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function FeaturesEditor() {
|
||||||
|
return import(/* webpackChunkName: "features/admin" */'../../admin/features-editor');
|
||||||
|
}
|
||||||
|
|
||||||
export function ThemeEditor() {
|
export function ThemeEditor() {
|
||||||
return import(/* webpackChunkName: "features/theme-editor" */'../../theme-editor');
|
return import(/* webpackChunkName: "features/theme-editor" */'../../theme-editor');
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,8 @@ export const SoapboxConfigRecord = ImmutableRecord({
|
||||||
* On some platforms this can be too blurry without additional configuration.
|
* On some platforms this can be too blurry without additional configuration.
|
||||||
*/
|
*/
|
||||||
mediaPreview: false,
|
mediaPreview: false,
|
||||||
|
/** Features overrides. */
|
||||||
|
features: ImmutableMap<string, boolean>(),
|
||||||
}, 'SoapboxConfig');
|
}, 'SoapboxConfig');
|
||||||
|
|
||||||
type SoapboxConfigMap = ImmutableMap<string, any>;
|
type SoapboxConfigMap = ImmutableMap<string, any>;
|
||||||
|
|
Ładowanie…
Reference in New Issue