You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-11-06 09:19:22 +02:00
Desktop: Add config screen to add, remove or enable, disable plugins
This commit is contained in:
@@ -3,17 +3,23 @@ import SideBar from './SideBar';
|
||||
import ButtonBar from './ButtonBar';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import bridge from '../../services/bridge';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import control_PluginsStates from './controls/PluginsStates';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const pathUtils = require('@joplin/lib/path-utils');
|
||||
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry');
|
||||
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const { EncryptionConfigScreen } = require('../EncryptionConfigScreen.min');
|
||||
const { ClipperConfigScreen } = require('../ClipperConfigScreen.min');
|
||||
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
|
||||
|
||||
const settingKeyToControl: any = {
|
||||
'plugins.states': control_PluginsStates,
|
||||
};
|
||||
|
||||
class ConfigScreenComponent extends React.Component<any, any> {
|
||||
|
||||
rowStyle_: any = null;
|
||||
@@ -27,6 +33,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
selectedSectionName: 'general',
|
||||
screenName: '',
|
||||
changedSettingKeys: [],
|
||||
needRestart: false,
|
||||
};
|
||||
|
||||
this.rowStyle_ = {
|
||||
@@ -41,6 +48,8 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
this.onCancelClick = this.onCancelClick.bind(this);
|
||||
this.onSaveClick = this.onSaveClick.bind(this);
|
||||
this.onApplyClick = this.onApplyClick.bind(this);
|
||||
this.renderLabel = this.renderLabel.bind(this);
|
||||
this.renderDescription = this.renderDescription.bind(this);
|
||||
}
|
||||
|
||||
async checkSyncConfig_() {
|
||||
@@ -261,6 +270,40 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
);
|
||||
}
|
||||
|
||||
private labelStyle(themeId: number) {
|
||||
const theme = themeStyle(themeId);
|
||||
return Object.assign({}, theme.textStyle, {
|
||||
display: 'block',
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize * 1.083333,
|
||||
fontWeight: 500,
|
||||
marginBottom: theme.mainPadding / 4,
|
||||
});
|
||||
}
|
||||
|
||||
private descriptionStyle(themeId: number) {
|
||||
const theme = themeStyle(themeId);
|
||||
return Object.assign({}, theme.textStyle, {
|
||||
color: theme.colorFaded,
|
||||
fontStyle: 'italic',
|
||||
maxWidth: '70em',
|
||||
marginTop: 5,
|
||||
});
|
||||
}
|
||||
|
||||
private renderLabel(themeId: number, label: string) {
|
||||
const labelStyle = this.labelStyle(themeId);
|
||||
return (
|
||||
<div style={labelStyle}>
|
||||
<label>{label}</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderDescription(themeId: number, description: string) {
|
||||
return description ? <div style={this.descriptionStyle(themeId)}>{description}</div> : null;
|
||||
}
|
||||
|
||||
settingToComponent(key: string, value: any) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
@@ -270,13 +313,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
marginBottom: theme.mainPadding,
|
||||
};
|
||||
|
||||
const labelStyle = Object.assign({}, theme.textStyle, {
|
||||
display: 'block',
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize * 1.083333,
|
||||
fontWeight: 500,
|
||||
marginBottom: theme.mainPadding / 4,
|
||||
});
|
||||
const labelStyle = this.labelStyle(this.props.themeId);
|
||||
|
||||
const subLabel = Object.assign({}, labelStyle, {
|
||||
display: 'block',
|
||||
@@ -297,13 +334,6 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
backgroundColor: theme.backgroundColor,
|
||||
};
|
||||
|
||||
const descriptionStyle = Object.assign({}, theme.textStyle, {
|
||||
color: theme.colorFaded,
|
||||
marginTop: 5,
|
||||
fontStyle: 'italic',
|
||||
maxWidth: '70em',
|
||||
});
|
||||
|
||||
const textInputBaseStyle = Object.assign({}, controlStyle, {
|
||||
fontFamily: theme.fontFamily,
|
||||
border: '1px solid',
|
||||
@@ -318,18 +348,39 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
});
|
||||
|
||||
const updateSettingValue = (key: string, value: any) => {
|
||||
// console.info(key + ' = ' + value);
|
||||
return shared.updateSettingValue(this, key, value);
|
||||
};
|
||||
const md = Setting.settingMetadata(key);
|
||||
if (md.needRestart) {
|
||||
this.setState({ needRestart: true });
|
||||
}
|
||||
shared.updateSettingValue(this, key, value);
|
||||
|
||||
// Component key needs to be key+value otherwise it doesn't update when the settings change.
|
||||
if (md.autoSave) {
|
||||
shared.scheduleSaveSettings(this);
|
||||
}
|
||||
};
|
||||
|
||||
const md = Setting.settingMetadata(key);
|
||||
|
||||
const descriptionText = Setting.keyDescription(key, 'desktop');
|
||||
const descriptionComp = descriptionText ? <div style={descriptionStyle}>{descriptionText}</div> : null;
|
||||
const descriptionComp = this.renderDescription(this.props.themeId, descriptionText);
|
||||
|
||||
if (md.isEnum) {
|
||||
if (settingKeyToControl[key]) {
|
||||
const SettingComponent = settingKeyToControl[key];
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
{this.renderLabel(this.props.themeId, md.label())}
|
||||
{this.renderDescription(this.props.themeId, md.description ? md.description() : null)}
|
||||
<SettingComponent
|
||||
metadata={md}
|
||||
value={value}
|
||||
themeId={this.props.themeId}
|
||||
onChange={(event: any) => {
|
||||
updateSettingValue(key, event.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (md.isEnum) {
|
||||
const items = [];
|
||||
const settingOptions = md.options();
|
||||
const array = this.keyValueToArray(settingOptions);
|
||||
@@ -568,12 +619,33 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
return output;
|
||||
}
|
||||
|
||||
onApplyClick() {
|
||||
shared.saveSettings(this);
|
||||
private restartMessage() {
|
||||
return _('The application must be restarted for these changes to take effect.');
|
||||
}
|
||||
|
||||
onSaveClick() {
|
||||
private async restartApp() {
|
||||
await Setting.saveAll();
|
||||
bridge().restart();
|
||||
}
|
||||
|
||||
private async checkNeedRestart() {
|
||||
if (this.state.needRestart) {
|
||||
const doItNow = await bridge().showConfirmMessageBox(this.restartMessage(), {
|
||||
buttons: [_('Do it now'), _('Later')],
|
||||
});
|
||||
|
||||
if (doItNow) await this.restartApp();
|
||||
}
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
shared.saveSettings(this);
|
||||
await this.checkNeedRestart();
|
||||
}
|
||||
|
||||
async onSaveClick() {
|
||||
shared.saveSettings(this);
|
||||
await this.checkNeedRestart();
|
||||
this.props.dispatch({ type: 'NAV_BACK' });
|
||||
}
|
||||
|
||||
@@ -621,6 +693,13 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
|
||||
const sections = shared.settingsSections({ device: 'desktop', settings });
|
||||
|
||||
const needRestartComp: any = this.state.needRestart ? (
|
||||
<div style={{ ...theme.textStyle, padding: 10, paddingLeft: 24, backgroundColor: theme.warningBackgroundColor, color: theme.color }}>
|
||||
{this.restartMessage()}
|
||||
<a style={{ ...theme.urlStyle, marginLeft: 10 }} href="#" onClick={() => { this.restartApp(); }}>{_('Restart now')}</a>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<SideBar
|
||||
@@ -630,6 +709,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
/>
|
||||
<div style={style}>
|
||||
{screenComp}
|
||||
{needRestartComp}
|
||||
<div style={containerStyle}>{settingComps}</div>
|
||||
<ButtonBar
|
||||
hasChanges={hasChanges}
|
||||
|
||||
255
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.tsx
Normal file
255
packages/app-desktop/gui/ConfigScreen/controls/PluginsStates.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import PluginService, { defaultPluginSetting, Plugins, PluginSetting, PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import styled from 'styled-components';
|
||||
import ToggleButton from '../../lib/ToggleButton/ToggleButton';
|
||||
import Button, { ButtonLevel } from '../../Button/Button';
|
||||
import bridge from '../../../services/bridge';
|
||||
import produce from 'immer';
|
||||
|
||||
const Root = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const TableRoot = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const InstallButton = styled(Button)`
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const CellRoot = styled.div`
|
||||
display: flex;
|
||||
background-color: ${props => props.theme.backgroundColor};
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 15px;
|
||||
border: 1px solid ${props => props.theme.dividerColor};
|
||||
border-radius: 6px;
|
||||
width: 250px;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 1px 1px 3px rgba(0,0,0,0.2);
|
||||
`;
|
||||
|
||||
const CellTop = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const CellContent = styled.div`
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const CellFooter = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const DevModeLabel = styled.div`
|
||||
border: 1px solid ${props => props.theme.color};
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
font-size: ${props => props.theme.fontSize * 0.75}px;
|
||||
color: ${props => props.theme.color};
|
||||
`;
|
||||
|
||||
const StyledName = styled.div`
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
color: ${props => props.theme.color};
|
||||
font-size: ${props => props.theme.fontSize}px;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledDescription = styled.div`
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
color: ${props => props.theme.colorFaded};
|
||||
font-size: ${props => props.theme.fontSize}px;
|
||||
line-height: 1.6em;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
value: any;
|
||||
themeId: number;
|
||||
onChange: Function;
|
||||
}
|
||||
|
||||
interface CellProps {
|
||||
item: PluginItem;
|
||||
themeId: number;
|
||||
onToggle: Function;
|
||||
onDelete: Function;
|
||||
}
|
||||
|
||||
interface PluginItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
deleted: boolean;
|
||||
devMode: boolean;
|
||||
}
|
||||
|
||||
function Cell(props: CellProps) {
|
||||
const { item } = props;
|
||||
|
||||
// For plugins in dev mode things like enabling/disabling or
|
||||
// uninstalling them doesn't make sense, as that should be done by
|
||||
// adding/removing them from wherever they were loaded from.
|
||||
|
||||
function renderToggleButton() {
|
||||
if (item.devMode) {
|
||||
return <DevModeLabel>DEV</DevModeLabel>;
|
||||
}
|
||||
|
||||
return <ToggleButton
|
||||
themeId={props.themeId}
|
||||
value={item.enabled}
|
||||
onToggle={() => props.onToggle({ item: props.item })}
|
||||
/>;
|
||||
}
|
||||
|
||||
function renderFooter() {
|
||||
if (item.devMode) return null;
|
||||
|
||||
return (
|
||||
<CellFooter>
|
||||
<Button level={ButtonLevel.Secondary} onClick={() => props.onDelete({ item: props.item })} title={_('Delete')}/>
|
||||
<div style={{ display: 'flex', flex: 1 }}/>
|
||||
</CellFooter>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CellRoot>
|
||||
<CellTop>
|
||||
<StyledName mb={'5px'}>{item.name} {item.deleted ? '(Deleted)' : ''}</StyledName>
|
||||
{renderToggleButton()}
|
||||
</CellTop>
|
||||
<CellContent>
|
||||
<StyledDescription>{item.description}</StyledDescription>
|
||||
</CellContent>
|
||||
{renderFooter()}
|
||||
</CellRoot>
|
||||
);
|
||||
}
|
||||
|
||||
function usePluginItems(plugins: Plugins, settings: PluginSettings): PluginItem[] {
|
||||
return useMemo(() => {
|
||||
const output: PluginItem[] = [];
|
||||
|
||||
for (const pluginId in plugins) {
|
||||
const plugin = plugins[pluginId];
|
||||
|
||||
const setting: PluginSetting = {
|
||||
...defaultPluginSetting(),
|
||||
...settings[pluginId],
|
||||
};
|
||||
|
||||
output.push({
|
||||
id: pluginId,
|
||||
name: plugin.manifest.name,
|
||||
description: plugin.manifest.description,
|
||||
enabled: setting.enabled,
|
||||
deleted: setting.deleted,
|
||||
devMode: plugin.devMode,
|
||||
});
|
||||
}
|
||||
|
||||
output.sort((a: PluginItem, b: PluginItem) => {
|
||||
return a.name < b.name ? -1 : +1;
|
||||
});
|
||||
|
||||
return output;
|
||||
}, [plugins, settings]);
|
||||
}
|
||||
|
||||
export default function(props: Props) {
|
||||
const pluginService = PluginService.instance();
|
||||
|
||||
const pluginSettings = useMemo(() => {
|
||||
return pluginService.unserializePluginSettings(props.value);
|
||||
}, [props.value]);
|
||||
|
||||
const onDelete = useCallback(async (event: any) => {
|
||||
const item: PluginItem = event.item;
|
||||
const confirm = await bridge().showConfirmMessageBox(_('Delete plugin "%s"?', item.name));
|
||||
if (!confirm) return;
|
||||
|
||||
const newSettings = produce(pluginSettings, (draft: PluginSettings) => {
|
||||
if (!draft[item.id]) draft[item.id] = defaultPluginSetting();
|
||||
draft[item.id].deleted = true;
|
||||
});
|
||||
|
||||
props.onChange({ value: pluginService.serializePluginSettings(newSettings) });
|
||||
}, [pluginSettings, props.onChange]);
|
||||
|
||||
const onToggle = useCallback((event: any) => {
|
||||
const item: PluginItem = event.item;
|
||||
|
||||
const newSettings = produce(pluginSettings, (draft: PluginSettings) => {
|
||||
if (!draft[item.id]) draft[item.id] = defaultPluginSetting();
|
||||
draft[item.id].enabled = !draft[item.id].enabled;
|
||||
});
|
||||
|
||||
props.onChange({ value: pluginService.serializePluginSettings(newSettings) });
|
||||
}, [pluginSettings, props.onChange]);
|
||||
|
||||
const onInstall = useCallback(async () => {
|
||||
const result = bridge().showOpenDialog({
|
||||
filters: [{ name: 'Joplin Plugin Archive', extensions: ['jpl'] }],
|
||||
});
|
||||
|
||||
const filePath = result && result.length ? result[0] : null;
|
||||
if (!filePath) return;
|
||||
|
||||
const plugin = await pluginService.installPlugin(filePath);
|
||||
|
||||
const newSettings = produce(pluginSettings, (draft: PluginSettings) => {
|
||||
draft[plugin.manifest.id] = defaultPluginSetting();
|
||||
});
|
||||
|
||||
props.onChange({ value: pluginService.serializePluginSettings(newSettings) });
|
||||
}, [pluginSettings, props.onChange]);
|
||||
|
||||
function renderCells(items: PluginItem[]) {
|
||||
const output = [];
|
||||
|
||||
for (const item of items) {
|
||||
if (item.deleted) continue;
|
||||
|
||||
output.push(<Cell
|
||||
key={item.id}
|
||||
item={item}
|
||||
themeId={props.themeId}
|
||||
onDelete={onDelete}
|
||||
onToggle={onToggle}
|
||||
/>);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const pluginItems = usePluginItems(pluginService.plugins, pluginSettings);
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<InstallButton level={ButtonLevel.Primary} onClick={onInstall} title={_('Install plugin')}/>
|
||||
<div style={{ display: 'flex', flex: 1 }}/>
|
||||
</div>
|
||||
<TableRoot>
|
||||
{renderCells(pluginItems)}
|
||||
</TableRoot>
|
||||
</Root>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const dialogs = require('./dialogs');
|
||||
const dialogs = require('./dialogs').default;
|
||||
const shared = require('@joplin/lib/components/shared/encryption-config-shared.js');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { ThemeAppearance } from '@joplin/lib/themes/type';
|
||||
import dialogs from '../../../dialogs';
|
||||
|
||||
const Note = require('@joplin/lib/models/Note.js');
|
||||
const { clipboard } = require('electron');
|
||||
@@ -29,7 +30,6 @@ const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
const dialogs = require('../../../dialogs');
|
||||
|
||||
const menuUtils = new MenuUtils(CommandService.instance());
|
||||
|
||||
|
||||
@@ -77,19 +77,19 @@ for (let i = 0; i < topLanguages.length; i++) {
|
||||
}
|
||||
|
||||
export interface EditorProps {
|
||||
value: string,
|
||||
searchMarkers: any,
|
||||
mode: string,
|
||||
style: any,
|
||||
codeMirrorTheme: any,
|
||||
readOnly: boolean,
|
||||
autoMatchBraces: boolean,
|
||||
keyMap: string,
|
||||
plugins: PluginStates,
|
||||
onChange: any,
|
||||
onScroll: any,
|
||||
onEditorContextMenu: any,
|
||||
onEditorPaste: any,
|
||||
value: string;
|
||||
searchMarkers: any;
|
||||
mode: string;
|
||||
style: any;
|
||||
codeMirrorTheme: any;
|
||||
readOnly: boolean;
|
||||
autoMatchBraces: boolean;
|
||||
keyMap: string;
|
||||
plugins: PluginStates;
|
||||
onChange: any;
|
||||
onScroll: any;
|
||||
onEditorContextMenu: any;
|
||||
onEditorPaste: any;
|
||||
}
|
||||
|
||||
function Editor(props: EditorProps, ref: any) {
|
||||
|
||||
@@ -4,6 +4,7 @@ const { buildStyle } = require('@joplin/lib/theme');
|
||||
export default function styles(props: NoteBodyEditorProps) {
|
||||
return buildStyle(['TinyMCE', props.style.width, props.style.height], props.themeId, (theme: any) => {
|
||||
const extraToolbarContainer = {
|
||||
boxSizing: 'content-box',
|
||||
backgroundColor: theme.backgroundColor3,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -32,6 +32,10 @@ interface Props {
|
||||
}
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div, span, a {
|
||||
/*color: ${(props: any) => props.theme.color};*/
|
||||
/*font-size: ${(props: any) => props.theme.fontSize}px;*/
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
const smalltalk = require('smalltalk');
|
||||
|
||||
class Dialogs {
|
||||
async alert(message, title = '') {
|
||||
async alert(message: string, title = '') {
|
||||
await smalltalk.alert(title, message);
|
||||
}
|
||||
|
||||
async confirm(message, title = '') {
|
||||
async confirm(message: string, title = '', options: any = {}) {
|
||||
try {
|
||||
await smalltalk.confirm(title, message);
|
||||
await smalltalk.confirm(title, message, options);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async prompt(message, title = '', defaultValue = '', options = null) {
|
||||
async prompt(message: string, title = '', defaultValue = '', options: any = null) {
|
||||
if (options === null) options = {};
|
||||
|
||||
try {
|
||||
@@ -28,4 +28,4 @@ class Dialogs {
|
||||
|
||||
const dialogs = new Dialogs();
|
||||
|
||||
module.exports = dialogs;
|
||||
export default dialogs;
|
||||
37
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.tsx
Normal file
37
packages/app-desktop/gui/lib/ToggleButton/ToggleButton.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import * as React from 'react';
|
||||
const ReactToggleButton = require('react-toggle-button');
|
||||
const Color = require('color');
|
||||
|
||||
interface Props {
|
||||
value: boolean;
|
||||
onToggle: Function;
|
||||
themeId: number;
|
||||
}
|
||||
|
||||
export default function(props: Props) {
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
return (
|
||||
<ReactToggleButton
|
||||
value={props.value}
|
||||
onToggle={props.onToggle}
|
||||
colors={{
|
||||
activeThumb: {
|
||||
base: Color(theme.color5).rgb().string(),
|
||||
},
|
||||
active: {
|
||||
base: Color(theme.backgroundColor5).alpha(0.7).rgb().string(),
|
||||
},
|
||||
}}
|
||||
trackStyle={{
|
||||
opacity: props.value ? 1 : 0.3,
|
||||
}}
|
||||
thumbStyle={{
|
||||
opacity: props.value ? 1 : 0.5,
|
||||
}}
|
||||
inactiveLabel=""
|
||||
activeLabel=""
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user