adds accordion, details query syntax, various small tweaks
This commit is contained in:
parent
f84ef2ee19
commit
4ca8379bec
32
.vscode/settings.json
vendored
32
.vscode/settings.json
vendored
@ -1,24 +1,24 @@
|
||||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.activeBackground": "#83a0ea",
|
||||
"activityBar.background": "#83a0ea",
|
||||
"activityBar.activeBackground": "#66b2f4",
|
||||
"activityBar.background": "#66b2f4",
|
||||
"activityBar.foreground": "#15202b",
|
||||
"activityBar.inactiveForeground": "#15202b99",
|
||||
"activityBarBadge.background": "#fae4ea",
|
||||
"activityBarBadge.foreground": "#15202b",
|
||||
"commandCenter.border": "#e7e7e799",
|
||||
"sash.hoverBorder": "#83a0ea",
|
||||
"statusBar.background": "#577ee3",
|
||||
"statusBar.foreground": "#e7e7e7",
|
||||
"statusBarItem.hoverBackground": "#83a0ea",
|
||||
"statusBarItem.remoteBackground": "#577ee3",
|
||||
"statusBarItem.remoteForeground": "#e7e7e7",
|
||||
"titleBar.activeBackground": "#577ee3",
|
||||
"titleBar.activeForeground": "#e7e7e7",
|
||||
"titleBar.inactiveBackground": "#577ee399",
|
||||
"titleBar.inactiveForeground": "#e7e7e799"
|
||||
"activityBarBadge.background": "#d00f76",
|
||||
"activityBarBadge.foreground": "#e7e7e7",
|
||||
"commandCenter.border": "#15202b99",
|
||||
"sash.hoverBorder": "#66b2f4",
|
||||
"statusBar.background": "#369af1",
|
||||
"statusBar.foreground": "#15202b",
|
||||
"statusBarItem.hoverBackground": "#1081e4",
|
||||
"statusBarItem.remoteBackground": "#369af1",
|
||||
"statusBarItem.remoteForeground": "#15202b",
|
||||
"titleBar.activeBackground": "#369af1",
|
||||
"titleBar.activeForeground": "#15202b",
|
||||
"titleBar.inactiveBackground": "#369af199",
|
||||
"titleBar.inactiveForeground": "#15202b99"
|
||||
},
|
||||
"peacock.remoteColor": "#577ee3",
|
||||
"peacock.remoteColor": "#369af1",
|
||||
"deno.enable": true,
|
||||
"deno.unstable": true,
|
||||
"deno.config": "./deno.jsonc",
|
||||
|
50
project-warstone/query syntax
Normal file
50
project-warstone/query syntax
Normal file
@ -0,0 +1,50 @@
|
||||
Start query: ?
|
||||
- by default, queries search the publication
|
||||
Query current object: ^
|
||||
Access child: .
|
||||
- this also maps over all items in a list and returns a list of the relevant children
|
||||
- if the parent is a list, it will return a list of just the specified child
|
||||
Select: []
|
||||
- Selects any of the items that matches the provided selector
|
||||
- Providing a number selects the ordinal item in the list
|
||||
- Providing a comparator selects items that match the comparator
|
||||
- Selectors can be separated by either commas for "and" or slashes for "or", but not both
|
||||
Comparators:
|
||||
= is similar to
|
||||
== is exactly the same
|
||||
> greater than
|
||||
< less than
|
||||
>= greater than or equal to
|
||||
<= less than or equal to
|
||||
!! is true/exists
|
||||
! is not
|
||||
/ or, allows matching one or more comparators at the same time
|
||||
Combiner: ()
|
||||
- Will only select items that match all of the selectors
|
||||
- Selectors can be separated by either commas for "and" or slashes for "or", but not both
|
||||
|
||||
--Templating--
|
||||
insertion: {{}}
|
||||
- Allows for queries to be wrapped in a templated string
|
||||
- Can be either a direct query or used in combination with the "_" character to reference a single query
|
||||
single query: <<?
|
||||
- Used the same as start query, but appended to the end of a templated string
|
||||
- Used in conjunction with "_" to reference the queried value
|
||||
query reference: _
|
||||
- used in conjunction with single query to reference the queried value
|
||||
separator: ::;;
|
||||
- used at the end of a template to indicate how to separate results when the results are more than one.
|
||||
- Whatever text is placed between in the middle is treated as the full separator, or in other words, if the separator doesn't have any spaces, then there will be no spaces between items in the final text
|
||||
- If no separator is provided, it will default to comma and space separation
|
||||
- placed at the end of the template but before the single query
|
||||
- should support any standard escaped characters such as newline
|
||||
|
||||
Examples:
|
||||
?core.weapon_abilities[name=Rapid Fire].body
|
||||
- this searches the publication 'core' object for weapon_abilities that have the name
|
||||
Sustained Hits {{?^sustained_hits}}
|
||||
- This formats the string with the queried values, which start the query within the same object that the field exists in. This would result in the string 'Sustained Hits 1'
|
||||
Anti-{{_.keyword}} {{_.value}}<<?^anti_abilities
|
||||
- This formats the string with the queried values, which start the query within the same object that the field exists in. This would result in the string 'Anti-Infantry 2+' or 'Anti-Infantry 2+, Anti-Monster 4+' if there are more than one matching values
|
||||
Anti-{{_.keyword}} {{_.value}}:://;;<<?^anti_abilities
|
||||
- This formats the string with the queried values, which start the query within the same object that the field exists in. This would result in the string 'Anti-Infantry 2+' or 'Anti-Infantry 2+//Anti-Monster 4+' if there are more than one matching values
|
@ -3,14 +3,16 @@ import { FieldType, FieldTypes, fieldTypeOptions, fieldTypesWithValues } from '.
|
||||
import { useObjectStateWrapper } from '../../hooks/useObjectState';
|
||||
import { ValueField } from './value-field';
|
||||
import { HelpPopper } from '../Poppables/help';
|
||||
import { Icon } from '../Icon';
|
||||
|
||||
interface IProps {
|
||||
update: (arg: FieldType) => void;
|
||||
field: FieldType;
|
||||
fieldName: string;
|
||||
deleteField: (arg: string) => void;
|
||||
}
|
||||
|
||||
export const FieldEditor: FC<IProps> = ({ update, field, fieldName }) => {
|
||||
export const FieldEditor: FC<IProps> = ({ update, field, fieldName, deleteField }) => {
|
||||
const { bindProperty, bindPropertyCheck } = useObjectStateWrapper(field, (e) => update(typeof e === 'function' ? e(field) : e))
|
||||
|
||||
const shouldShowValueField = useCallback(() => fieldTypesWithValues.includes(field.type) || field.isConstant, [field.isConstant, field.type]);
|
||||
@ -22,7 +24,7 @@ export const FieldEditor: FC<IProps> = ({ update, field, fieldName }) => {
|
||||
return (
|
||||
<li className="odd:bg-black/50">
|
||||
<p>{fieldName}</p>
|
||||
<div className=" flex gap-x-4 items-center p-2">
|
||||
<div className=" flex gap-x-4 items-center p-2 w-full">
|
||||
<label className="w-min">
|
||||
Field Type:
|
||||
<select className="capitalize" {...bindProperty('type')}>
|
||||
@ -40,10 +42,20 @@ export const FieldEditor: FC<IProps> = ({ update, field, fieldName }) => {
|
||||
<p className="text-sm">Constant values can't be overwritten in publications. When a dice field is set to a constant value, it instead rolls a dice of that value whenever this field is displayed (unless exported). This could be useful for a randomly generated scenario or for cards being drawn as the dice value will automatically be determined by the dice roll.</p>
|
||||
</HelpPopper>
|
||||
</span>
|
||||
<label className="w-min">
|
||||
Minimum:
|
||||
<input className="w-12" type="number" {...bindProperty('minimum')} />
|
||||
</label>
|
||||
<label className="w-min">
|
||||
Limit:
|
||||
<input className="w-12" type="number" {...bindProperty('limit')} />
|
||||
</label>
|
||||
<HelpPopper>
|
||||
<p className="text-sm">Minimum and Limit apply to the number of entries allowed for this field, not the maximum and minimum value. Set the minimum to 0 to make a field optional. Set the limit to 0 to allow for unlimited entries.</p>
|
||||
</HelpPopper>
|
||||
<button className="no-default self-end ml-auto" onClick={() => deleteField(fieldName)}>
|
||||
<Icon className="svg-red-700 hover:svg-red-500 trash-can w-6 h-6" icon="trash"></Icon>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
|
@ -135,8 +135,7 @@ export const SchemaBuilder: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel basis-1/3">
|
||||
<SchemaViewer schema={schema} onTypeClick={selectTypeForEdit} />
|
||||
<div className="flex gap-2 mt-2">
|
||||
<div className="flex gap-2 mb-2">
|
||||
<button
|
||||
onClick={saveSchema}
|
||||
disabled={lastSaved === schema}
|
||||
@ -151,6 +150,7 @@ export const SchemaBuilder: FC = () => {
|
||||
Discard Changes
|
||||
</button>
|
||||
</div>
|
||||
<SchemaViewer schema={schema} onTypeClick={selectTypeForEdit} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { FC, useCallback } from 'react'
|
||||
import { FieldType, FieldTypes, Schema, TypeType, fieldTypesWithValues } from '../../types/schema'
|
||||
import { Truncate } from '../Poppables/truncation';
|
||||
import { Accordion, AccordionContent } from '../../lib/accordion';
|
||||
|
||||
interface IProps {
|
||||
schema: Schema;
|
||||
@ -40,25 +41,30 @@ export const SchemaViewer: FC<IProps> = ({ schema, onTypeClick }) => {
|
||||
</ul>
|
||||
<hr />
|
||||
<p className="font-bold italic">Types</p>
|
||||
<ul className="rounded-lg overflow-hidden">
|
||||
<ul className="rounded-lg overflow-hidden grid">
|
||||
{Object.entries(schema.types).map(([typeKey, type]) => (
|
||||
<li
|
||||
key={'type viewer' + typeKey}
|
||||
onClick={() => onTypeClick && onTypeClick(typeKey, type)}
|
||||
// onClick={() => onTypeClick && onTypeClick(typeKey, type)}
|
||||
data-clickable={!!onTypeClick}
|
||||
className="odd:bg-black/50 p-2 data-[clickable=true]:cursor-pointer"
|
||||
className="odd:bg-black/50 p-2 group overflow-hidden"
|
||||
>
|
||||
<p className="mb-2 font-bold">{typeKey}</p>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{Object.entries(type).map(([fieldKey, field]) => (
|
||||
<div key={'field viewer' + fieldKey} className="rounded-lg border border-olive-drab p-2">
|
||||
<p className="font-bold">{fieldKey}</p>
|
||||
<p className="font-thin capitalize text-xs">{field.type}</p>
|
||||
<p className="font-thin capitalize text-xs">Maximum entries: {field.limit === 0 ? 'unlimited ' : field.limit}</p>
|
||||
{(field.isConstant || fieldTypesWithValues.includes(field.type)) && <p className="font-thin capitalize text-xs">{createValueLable(field)} <Truncate>{field.value}</Truncate></p>}
|
||||
<Accordion
|
||||
title={<p className="group-data-[expanded]/controlled:mb-2 transition-all font-bold">{typeKey}</p>}
|
||||
>
|
||||
<AccordionContent>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{Object.entries(type).map(([fieldKey, field]) => (
|
||||
<div key={'field viewer' + fieldKey} className="rounded-lg border border-olive-drab p-2">
|
||||
<p className="font-bold">{fieldKey}</p>
|
||||
<p className="font-thin capitalize text-xs">{field.type}</p>
|
||||
<p className="font-thin capitalize text-xs">Maximum entries: {field.limit === 0 ? 'unlimited ' : field.limit}</p>
|
||||
{(field.isConstant || fieldTypesWithValues.includes(field.type)) && <p className="font-thin capitalize text-xs">{createValueLable(field)} <Truncate>{field.value}</Truncate></p>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</Accordion>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -30,6 +30,7 @@ export const TypeEditor: FCC<IProps> = ({ saveType, name, type: passedType }) =>
|
||||
value: '',
|
||||
isConstant: false,
|
||||
limit: 1,
|
||||
minimum: 1,
|
||||
}
|
||||
});
|
||||
resetPropertyName();
|
||||
@ -43,14 +44,22 @@ export const TypeEditor: FCC<IProps> = ({ saveType, name, type: passedType }) =>
|
||||
passedType && setType(passedType);
|
||||
}, [passedType, setType]);
|
||||
|
||||
const deleteField = useCallback((name: string) => {
|
||||
setType(t => {
|
||||
const fields = {...t};
|
||||
delete fields[name];
|
||||
return fields;
|
||||
})
|
||||
}, [setType])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="subheader">{passedType ? 'Editing' : 'Creating'} type "{name}"</p>
|
||||
<input type="text" {...bindPropertyName} />
|
||||
<button disabled={!propertyName} onClick={addField}>Add Field</button>
|
||||
<ul className="rounded-lg overflow-hidden">
|
||||
{Object.entries(type).filter(([k]) => !constantProperties.includes(k)).map(([key, value]) => (
|
||||
<FieldEditor field={value} update={updateField(key)} fieldName={key} />
|
||||
{Object.entries(type).reverse().filter(([k]) => !constantProperties.includes(k)).map(([key, value]) => (
|
||||
<FieldEditor field={value} update={updateField(key)} fieldName={key} deleteField={deleteField} />
|
||||
))}
|
||||
</ul>
|
||||
<div>
|
||||
|
@ -61,7 +61,7 @@ export const ValueField: FC<IValueProps> = ({ type, bind }) => {
|
||||
)
|
||||
case FieldTypes.text:
|
||||
return (
|
||||
<label className="w-min">Value:<input type="number" {...bind} /></label>
|
||||
<label className="w-min">Value:<input type="text" {...bind} /></label>
|
||||
)
|
||||
case FieldTypes.select:
|
||||
return (
|
||||
|
@ -5,12 +5,14 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
|
||||
name: {
|
||||
isConstant: false,
|
||||
limit: 1,
|
||||
minimum: 1,
|
||||
type: FieldTypes.text,
|
||||
value: ''
|
||||
},
|
||||
body: {
|
||||
isConstant: false,
|
||||
limit: 0,
|
||||
minimum: 1,
|
||||
type: FieldTypes['long text'],
|
||||
value: ''
|
||||
},
|
||||
@ -19,6 +21,7 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
|
||||
steps: {
|
||||
isConstant: false,
|
||||
limit: 0,
|
||||
minimum: 1,
|
||||
type: FieldTypes.type,
|
||||
value: 'section'
|
||||
}
|
||||
@ -27,12 +30,14 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
|
||||
name: {
|
||||
isConstant: false,
|
||||
limit: 1,
|
||||
minimum: 1,
|
||||
type: FieldTypes.text,
|
||||
value: ''
|
||||
},
|
||||
link: {
|
||||
isConstant: false,
|
||||
limit: 1,
|
||||
minimum: 1,
|
||||
type: FieldTypes.text,
|
||||
value: ''
|
||||
},
|
||||
@ -41,6 +46,7 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
|
||||
items: {
|
||||
isConstant: false,
|
||||
limit: 0,
|
||||
minimum: 1,
|
||||
type: FieldTypes['long text'],
|
||||
value: ''
|
||||
}
|
||||
@ -49,6 +55,7 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
|
||||
columns: {
|
||||
isConstant: false,
|
||||
limit: 0,
|
||||
minimum: 1,
|
||||
type: FieldTypes.any,
|
||||
value: ''
|
||||
}
|
||||
@ -57,12 +64,14 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
|
||||
rows: {
|
||||
isConstant: false,
|
||||
limit: 0,
|
||||
minimum: 1,
|
||||
type: FieldTypes.type,
|
||||
value: 'tableRow'
|
||||
},
|
||||
header: {
|
||||
isConstant: false,
|
||||
limit: 1,
|
||||
minimum: 0,
|
||||
type: FieldTypes.type,
|
||||
value: 'tableRow'
|
||||
}
|
||||
|
@ -42,7 +42,11 @@
|
||||
}
|
||||
|
||||
button:not(.no-default) {
|
||||
@apply interactive bg-olive-drab p-1
|
||||
@apply interactive p-1
|
||||
}
|
||||
|
||||
button:not([class*="bg-"]):not(.no-default) {
|
||||
@apply bg-olive-drab
|
||||
}
|
||||
|
||||
.interactive {
|
||||
@ -66,11 +70,14 @@
|
||||
animation: fade 300ms forwards ease-in reverse;
|
||||
}
|
||||
|
||||
.trash-can, .anvil {
|
||||
.trash-can,
|
||||
.anvil {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.trash-can path.trash-lid, .anvil .anvil-base, .anvil .anvil-body {
|
||||
.trash-can path.trash-lid,
|
||||
.anvil .anvil-base,
|
||||
.anvil .anvil-body {
|
||||
transition: 300ms transform, 300ms rotate, 300ms fill, 300ms stroke;
|
||||
}
|
||||
|
||||
@ -82,6 +89,7 @@
|
||||
.anvil:hover .anvil-base {
|
||||
transform: translate(0px, 5%);
|
||||
}
|
||||
|
||||
.anvil:hover .anvil-body {
|
||||
transform: translate(0px, -5%);
|
||||
}
|
||||
|
68
project-warstone/src/lib/accordion/index.tsx
Normal file
68
project-warstone/src/lib/accordion/index.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { FC, PropsWithChildren, ReactNode, useCallback, useState } from 'react'
|
||||
import { FCC } from '../../types';
|
||||
|
||||
interface IProps {
|
||||
expandOnHover?: boolean;
|
||||
expanded?: boolean;
|
||||
title?: ReactNode;
|
||||
}
|
||||
|
||||
export const Accordion: FCC<IProps> = ({ children, expandOnHover, expanded, title }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-expanded={open || expanded}
|
||||
data-expandOnHover={expandOnHover}
|
||||
className={(expandOnHover ? 'group/hover' : 'group/controlled') + ' group'}
|
||||
onClick={() => !title && !expandOnHover && setOpen(!open)}
|
||||
>
|
||||
{!!title && (
|
||||
<div className="flex justify-between cursor-pointer" onClick={() => !expandOnHover && setOpen(!open)}>
|
||||
{title}
|
||||
<div
|
||||
className={`
|
||||
group-hover/hover:-rotate-180
|
||||
group-data-[expanded]:-rotate-180
|
||||
transition-transform
|
||||
duration-500
|
||||
grid
|
||||
rounded-full
|
||||
h-min
|
||||
mr-2
|
||||
mt-1
|
||||
scale-y-50
|
||||
`}
|
||||
>
|
||||
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center"></span>
|
||||
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center"></span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const AccordionContent: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [height, setHeight] = useState(0);
|
||||
|
||||
const updateRef = useCallback((node: HTMLDivElement | null) => {
|
||||
if (node) {
|
||||
setHeight(node.clientHeight)
|
||||
} else {
|
||||
setHeight(0)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const Child = () => <div className="absolute bottom-0 w-full" ref={updateRef}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
{<Child />}
|
||||
<span style={{ ['--v-height' as never]: height + 'px' }} className="w-0 block h-0 group-hover/hover:h-variable group-data-[expanded]/controlled:h-variable transition-all duration-700" />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -7,6 +7,7 @@ export type FieldType = {
|
||||
value: string;
|
||||
isConstant: boolean;
|
||||
limit: number;
|
||||
minimum: number;
|
||||
};
|
||||
|
||||
export type TypeType = Record<string, FieldType>;
|
||||
|
@ -50,6 +50,9 @@ export default {
|
||||
},
|
||||
container: {
|
||||
center: true
|
||||
},
|
||||
height: {
|
||||
variable: 'var(--v-height)'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
102
project-warstone/temp.json
Normal file
102
project-warstone/temp.json
Normal file
@ -0,0 +1,102 @@
|
||||
{
|
||||
"id": "286f4c18-d280-444b-8d7e-9a3dd09f64ef",
|
||||
"name": "Warhammer 40,000 10e v1",
|
||||
"types": {
|
||||
"Wargear_Profile": {
|
||||
"name": {
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": "1"
|
||||
},
|
||||
"abilities": {
|
||||
"type": "select",
|
||||
"value": "Assault,Pistol,Rapid Fire,Torrent,Ignores Cover,Lethal Hits,Twin-linked,Lance,Indirect Fire,Blast,Melta,Precision,Heavy,Hazardous,Sustained Hits:Sustained Hits {{?^sustained_hits}},Extra Attacks,Devastating Wounds,Anti:Anti-{{_.keyword}} {{_.count}}+<<?^sustained_hits",
|
||||
"isConstant": false,
|
||||
"limit": 0,
|
||||
"minimum": "1"
|
||||
},
|
||||
"range": {
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
},
|
||||
"attacks": {
|
||||
"type": "@select",
|
||||
"value": "number,dice",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
},
|
||||
"bs_or_ws": {
|
||||
"type": "select",
|
||||
"value": "ballistic_skill,weapon_skill",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
},
|
||||
"bs_ws": {
|
||||
"type": "number",
|
||||
"value": "",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
},
|
||||
"bs_ws_formatted": {
|
||||
"type": "text",
|
||||
"value": "{{?^bs_ws}}+",
|
||||
"isConstant": true,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
},
|
||||
"strength": {
|
||||
"type": "@select",
|
||||
"value": "number,dice",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
},
|
||||
"ap": {
|
||||
"type": "@select",
|
||||
"value": "number,dice",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
},
|
||||
"damage": {
|
||||
"type": "@select",
|
||||
"value": "number,dice",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
},
|
||||
"additional_abilities": {
|
||||
"type": "long text",
|
||||
"value": "",
|
||||
"isConstant": false,
|
||||
"limit": 0,
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"anti": {
|
||||
"keyword": {
|
||||
"type": "number",
|
||||
"value": "",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
},
|
||||
"count": {
|
||||
"type": "number",
|
||||
"value": "",
|
||||
"isConstant": false,
|
||||
"limit": 1,
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"templates": {}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user