blob: c062e63e68258801ee180cd36699a230ea3a668b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Button,
Card,
Collapse,
ControlGroup,
FormGroup,
HTMLSelect,
InputGroup,
NumericInput,
Switch,
} from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React, { useState } from 'react';
import { Rule, RuleUtil } from '../../utils/load-rule';
import { SuggestibleInput } from '../suggestible-input/suggestible-input';
import './rule-editor.scss';
const PERIOD_SUGGESTIONS: string[] = ['P1D', 'P7D', 'P1M', 'P1Y', 'P1000Y'];
export interface RuleEditorProps {
rule: Rule;
tiers: any[];
onChange: (newRule: Rule) => void;
onDelete: () => void;
moveUp: (() => void) | undefined;
moveDown: (() => void) | undefined;
}
export const RuleEditor = React.memo(function RuleEditor(props: RuleEditorProps) {
const { rule, onChange, tiers, onDelete, moveUp, moveDown } = props;
const [isOpen, setIsOpen] = useState(true);
function removeTier(key: string) {
const newTierReplicants = { ...rule.tieredReplicants };
delete newTierReplicants[key];
const newRule = { ...rule, tieredReplicants: newTierReplicants };
onChange(newRule);
}
function addTier() {
let newTierName = tiers[0];
if (rule.tieredReplicants) {
for (const tier of tiers) {
if (rule.tieredReplicants[tier] === undefined) {
newTierName = tier;
break;
}
}
}
onChange(RuleUtil.addTieredReplicant(rule, newTierName, 1));
}
function renderTiers() {
const tieredReplicants = rule.tieredReplicants;
if (!tieredReplicants) return;
const ruleTiers = Object.keys(tieredReplicants).sort();
return ruleTiers.map(tier => {
return (
<ControlGroup key={tier}>
<Button minimal style={{ pointerEvents: 'none' }}>
Replicants:
</Button>
<NumericInput
value={tieredReplicants[tier]}
onValueChange={(v: number) => {
if (isNaN(v)) return;
onChange(RuleUtil.addTieredReplicant(rule, tier, v));
}}
min={1}
max={256}
/>
<Button minimal style={{ pointerEvents: 'none' }}>
Tier:
</Button>
<HTMLSelect
fill
value={tier}
onChange={(e: any) =>
onChange(RuleUtil.renameTieredReplicants(rule, tier, e.target.value))
}
>
<option key={tier} value={tier}>
{tier}
</option>
{tiers
.filter(t => t !== tier && !tieredReplicants[t])
.map(t => {
return (
<option key={t} value={t}>
{t}
</option>
);
})}
</HTMLSelect>
<Button
disabled={ruleTiers.length === 1}
onClick={() => removeTier(tier)}
icon={IconNames.TRASH}
/>
</ControlGroup>
);
});
}
function renderTierAdder() {
const { rule, tiers } = props;
if (Object.keys(rule.tieredReplicants || {}).length >= Object.keys(tiers).length) return;
return (
<FormGroup className="right">
<Button onClick={addTier} minimal icon={IconNames.PLUS}>
Add a tier
</Button>
</FormGroup>
);
}
return (
<div className="rule-editor">
<div className="title">
<Button
className="left"
minimal
rightIcon={isOpen ? IconNames.CARET_DOWN : IconNames.CARET_RIGHT}
onClick={() => setIsOpen(!isOpen)}
>
{RuleUtil.ruleToString(rule)}
</Button>
<div className="spacer" />
{moveUp && <Button minimal icon={IconNames.ARROW_UP} onClick={moveUp} />}
{moveDown && <Button minimal icon={IconNames.ARROW_DOWN} onClick={moveDown} />}
<Button minimal icon={IconNames.TRASH} onClick={onDelete} />
</div>
<Collapse isOpen={isOpen}>
<Card elevation={2}>
<FormGroup>
<ControlGroup>
<HTMLSelect
value={rule.type}
onChange={(e: any) => onChange(RuleUtil.changeRuleType(rule, e.target.value))}
>
{RuleUtil.TYPES.map(type => {
return (
<option key={type} value={type}>
{type}
</option>
);
})}
</HTMLSelect>
{RuleUtil.hasPeriod(rule) && (
<SuggestibleInput
value={rule.period || ''}
onValueChange={period => {
if (typeof period === 'undefined') return;
// Ensure the period is upper case and does not contain anytihng but the allowed chars
period = period.toUpperCase().replace(/[^PYMDTHS0-9]/g, '');
onChange(RuleUtil.changePeriod(rule, period));
}}
placeholder={PERIOD_SUGGESTIONS[0]}
suggestions={PERIOD_SUGGESTIONS}
/>
)}
{RuleUtil.hasIncludeFuture(rule) && (
<Switch
className="include-future"
checked={rule.includeFuture || false}
label="Include future"
onChange={() => {
onChange(RuleUtil.changeIncludeFuture(rule, !rule.includeFuture));
}}
/>
)}
{RuleUtil.hasInterval(rule) && (
<InputGroup
value={rule.interval || ''}
onChange={(e: any) => onChange(RuleUtil.changeInterval(rule, e.target.value))}
placeholder="2010-01-01/2020-01-01"
/>
)}
</ControlGroup>
</FormGroup>
{RuleUtil.hasTieredReplicants(rule) && (
<FormGroup>
{renderTiers()}
{renderTierAdder()}
</FormGroup>
)}
</Card>
</Collapse>
</div>
);
});