lodash#clamp TypeScript Examples
The following examples show how to use
lodash#clamp.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: data-cell.ts From S2 with MIT License | 6 votes |
/**
* 计算柱图的 scale 函数(两种情况)
*
* min_________x_____0___________________________max
* |<----r---->|
*
* 0_________________min_________x_______________max
* |<-------------r------------->|
*
* @param minValue in current field values
* @param max in current field values
*/
private getIntervalScale(minValue = 0, maxValue = 0) {
minValue = parseNumberWithPrecision(minValue);
maxValue = parseNumberWithPrecision(maxValue);
const realMin = minValue >= 0 ? 0 : minValue;
const distance = maxValue - realMin || 1;
return (current: number) =>
// max percentage shouldn't be greater than 100%
// min percentage shouldn't be less than 0%
clamp((current - realMin) / distance, 0, 1);
}
Example #2
Source File: EventStreamGraph.tsx From next-basics with GNU General Public License v3.0 | 6 votes |
private transform(dx: number, dy: number): void {
const nodeWidth = styleConfig.node.width;
const maxOffsetX = this.nodesContainerWidth - nodeWidth / 2;
const maxOffsetY = this.nodesContainerHeight - nodeWidth / 2;
const minOffsetX = -this.canvas.node().offsetWidth + nodeWidth / 2;
const minOffsetY = -this.canvas.node().offsetHeight + nodeWidth / 2;
const resultX = this.offsetX + dx;
const resultY = this.offsetY + dy;
this.offsetX = clamp(resultX, minOffsetX, maxOffsetX);
this.offsetY = clamp(resultY, minOffsetY, maxOffsetY);
const transformToNodes = `translate(${-this.offsetX}px, ${-this
.offsetY}px)`;
const transform = zoomIdentity.translate(-this.offsetX, -this.offsetY);
this.linksLayer.attr("transform", transform.toString());
this.nodesLayer.style("transform", transformToNodes);
}
Example #3
Source File: Accelerometer.ts From disco-cube-daemon with MIT License | 6 votes |
constructor() {
const port = new SerialPort("/dev/ttyACM0", {
baudRate: 115200,
});
const parser = new Readline();
port.pipe(parser);
parser.on("data", (data: string) => {
const parts = data.trim().split(",").map(s => clamp(Number.parseInt(s) / 1000, -1, 1)).map(n => Number.isNaN(n) ? 0 : n)
if (parts.length != 3) return;
this.accel = parts;
});
}
Example #4
Source File: getNumThreads.ts From nextclade with MIT License | 6 votes |
export function guessNumThreads() {
const memoryBytesAvailable = getMemoryBytesAvailable()
if (memoryBytesAvailable && Number.isFinite(memoryBytesAvailable)) {
const numThreadsMax = Math.floor(memoryBytesAvailable / MEMORY_BYTES_PER_THREAD_MINIMUM)
const numThreads = clamp(numThreadsMax, MINIMUM_NUM_THREADS, MAXIMUM_NUM_THREADS)
return { memoryAvailable: memoryBytesAvailable, numThreads }
}
return undefined
}
Example #5
Source File: BitcoinNode.ts From sapio-studio with Mozilla Public License 2.0 | 6 votes |
async periodic_check() {
const contract = this.props.current_contract;
console.info('PERIODIC CONTRACT CHECK');
if (!contract.should_update()) {
// poll here faster
this.next_periodic_check = setTimeout(
this.periodic_check.bind(this),
1000
);
return;
}
const status = await this.get_transaction_status(contract);
const state = compute_impossible(
derive_state(status, this.props.current_contract),
this.props.current_contract
);
store.dispatch(load_status({ status, state }));
if (this.mounted) {
const freq = selectNodePollFreq(store.getState());
const period = clamp(freq ?? 0, 5, 60 * 5);
console.info('NEXT PERIODIC CONTRACT CHECK ', period, ' SECONDS');
this.next_periodic_check = setTimeout(
this.periodic_check.bind(this),
1000 * period
);
}
}
Example #6
Source File: rasa-chatbot.tsx From covid-19 with MIT License | 5 votes |
customMessageDelay = (message: string) => {
if (message === 'undefined') return 0
const delay = message.length * 5
return clamp(delay, 500, 1500)
}
Example #7
Source File: propertyMerge.ts From next-core with GNU General Public License v3.0 | 5 votes |
function propertyMergeAllOfArray(
{ baseValue, context, proxies }: MergeBase,
object: Record<string, unknown>
): unknown[] {
// Use an approach like template-literal's quasis:
// `quasi0${0}quais1${1}quasi2...`
// Every quasi can be merged with multiple items.
const computedBaseValue = Array.isArray(baseValue)
? (computeRealValue(baseValue, context, true, {
$$lazyForUseBrick: true,
}) as unknown[])
: [];
const quasis: unknown[][] = [];
const size = computedBaseValue.length + 1;
for (let i = 0; i < size; i += 1) {
quasis.push([]);
}
for (const proxy of proxies as MergeablePropertyProxyOfArray[]) {
let position: number;
switch (proxy.mergeMethod) {
case "append":
position = computedBaseValue.length;
break;
case "prepend":
position = 0;
break;
case "insertAt":
// Defaults to `-1`.
position = (proxy.mergeArgs as [number])?.[0] ?? -1;
if (position < 0) {
// It's counted from the end if position is negative.
position += quasis.length;
}
position = clamp(position, 0, computedBaseValue.length);
break;
// istanbul ignore next: should never reach
default:
throw new TypeError(
`unsupported mergeMethod: "${proxy.mergeMethod}" for mergeType "${proxy.mergeType}"`
);
}
let patchValue = object[proxy.$$reversedRef] as unknown[];
if (!Array.isArray(patchValue)) {
patchValue = [];
}
quasis[position].push(...patchValue);
}
return quasis.flatMap((item, index) =>
index < computedBaseValue.length
? item.concat(computedBaseValue[index])
: item
);
}
Example #8
Source File: roam-vim-panel.ts From roam-toolkit with MIT License | 5 votes |
private static at(panelIndex: PanelIndex): VimRoamPanel {
panelIndex = clamp(panelIndex, 0, state.panelOrder.length - 1)
return VimRoamPanel.get(state.panelOrder[panelIndex])
}
Example #9
Source File: BitcoinStatusBar.tsx From sapio-studio with Mozilla Public License 2.0 | 5 votes |
export function BitcoinStatusBar(props: BitcoinStatusBarProps) {
const theme = useTheme();
const freq = useSelector(selectNodePollFreq);
const [balance, setBalance] = React.useState<number>(0);
const [blockchaininfo, setBlockchaininfo] = React.useState<any>(null);
React.useEffect(() => {
let next: ReturnType<typeof setTimeout> | null = null;
let mounted = true;
const periodic_update_stats = async () => {
next = null;
try {
const balance = await props.api.check_balance();
setBalance(balance);
} catch (err) {
console.error(err);
setBalance(0);
}
try {
const info = await props.api.blockchaininfo();
console.log(balance);
setBlockchaininfo(info);
} catch (err) {
console.error(err);
setBlockchaininfo(null);
}
if (mounted) {
let prefs = freq;
prefs = clamp(prefs ?? 0, 5, 5 * 60);
console.log('StatusBar', 'NEXT PERIODIC CHECK IN ', prefs);
next = setTimeout(periodic_update_stats, prefs * 1000);
}
};
let prefs = freq;
prefs = clamp(prefs ?? 0, 5, 5 * 60);
next = setTimeout(periodic_update_stats, prefs * 1000);
return () => {
mounted = false;
if (next !== null) clearTimeout(next);
};
}, []);
const network = blockchaininfo?.chain ?? 'disconnected';
const headers = blockchaininfo?.headers ?? '?';
const blocks = blockchaininfo?.headers ?? '?';
return (
<Paper
square={true}
sx={{
top: 'auto',
bottom: 0,
}}
className="BitcoinStatusBar Draggable"
style={{
background: theme.palette.background.default,
color: theme.palette.info.main,
}}
>
<Toolbar variant="dense">
<Typography variant="h6" color="inherit" component="div">
<div>chain: {network}</div>
</Typography>
<Typography variant="h6" color="inherit" component="div">
<div style={{ marginLeft: '0.5em' }}>
balance: {balance} BTC
</div>
</Typography>
<Typography variant="h6" color="inherit" component="div">
<div style={{ marginLeft: '0.5em' }}>
processed: {blocks}/{headers}
</div>
</Typography>
</Toolbar>
</Paper>
);
}
Example #10
Source File: SearchWizardForm.tsx From one-platform with MIT License | 4 votes |
export default function SearchWizardForm(props: any) {
const { appId, loading: appLoading } = useContext(AppContext);
const { searchConfig, loading: searchConfigLoading } = useSearchConfig(appId);
const [currentStepId, setCurrentStepId] = useState<number>(0);
const { actions, state } = useStateMachine({ saveState, overrideState });
const [defaultValues, setDefaultValues] = useState<App.SearchConfig>();
useEffect( () => {
setCurrentStepId( state.savedStepId ?? 0 );
}, [state.savedStepId] );
useEffect(() => {
if (!isEmpty(searchConfig)) {
setDefaultValues( searchConfig );
if ( isEmpty( state.formData ) || state.appId !== appId ) {
actions.saveState( { appId, formData: { ...searchConfig } } );
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [actions, appId, searchConfig] );
const onMove = useCallback(( stepId: number ) => {
setCurrentStepId( stepId );
actions.saveState({
savedStepId: ( !state.savedStepId || state.savedStepId < stepId )
? stepId
: state.savedStepId,
});
}, [actions, state.savedStepId]);
const onNext = useCallback( () => {
onMove( clamp( currentStepId + 1, 0, 3 ) );
}, [currentStepId, onMove] );
const onBack = useCallback( () => {
onMove( clamp( currentStepId - 1, 0, 3 ) );
}, [currentStepId, onMove])
const saveSearchConfig = useCallback( () => {
const { formData } = state;
updateSearchConfigService( appId, formData )
.then( res => {
window.OpNotification?.success({
subject: 'Search Configuration Saved Successfully!',
} );
actions.saveState( { savedStepId: 0 } );
} )
.catch( err => {
window.OpNotification?.danger({
subject: 'An error occurred when saving the Search Config.',
body: 'Please try again later.',
});
console.error(err);
} );
}, [actions, appId, state] );
const resetForm = useCallback(() => {
actions.overrideState( {
formData: defaultValues,
savedStepId: 0,
} );
}, [actions, defaultValues] );
const stepTabs = useMemo( () => {
return steps.map( ( step, index ) => {
const isStepActive = currentStepId === index;
const stepStyles: CSSProperties = {
marginRight: '0.5rem',
borderWidth: '1px',
borderStyle: 'solid',
borderRadius: '2rem',
width: '1.5rem',
lineHeight: '1.5rem',
display: 'inline-grid',
placeContent: 'center',
};
if ( isStepActive ) {
stepStyles.color = '#fff';
stepStyles.borderColor = stepStyles.backgroundColor =
'var(--pf-global--primary-color--100)';
}
return (
<FlexItem key={index} flex={{ default: 'flex_1' }}>
<Button isBlock variant="plain" isDisabled={state.savedStepId < index}>
<Text
className={ isStepActive
? 'pf-u-primary-color-100 pf-u-font-weight-bold'
: ''
}
>
<span style={{ ...stepStyles }}>{index + 1}</span>
{step.label}
</Text>
</Button>
</FlexItem>
);});
}, [currentStepId, state.savedStepId] );
const currentStep = useMemo(() => {
const Step = steps[currentStepId].component;
const props: IConfigureSearchStepProps = {
onNext,
onBack,
onReset: resetForm,
};
if (currentStepId === 0) {
props.onBack = undefined;
}
if (currentStepId === steps.length - 1) {
props.onNext = saveSearchConfig;
props.nextButtonText = 'Save'
}
return <Step {...props} />;
}, [currentStepId, onBack, onNext, resetForm, saveSearchConfig] );
return (
<>
<Stack hasGutter>
<StackItem>
<Header title="Configure Search" />
</StackItem>
{(appLoading || searchConfigLoading) && <Loader />}
{!appLoading && !searchConfigLoading && (
<>
<StackItem>
<Flex justifyContent={{ default: 'justifyContentSpaceAround' }}>
{stepTabs}
</Flex>
</StackItem>
<StackItem>
<Card isRounded>
<CardBody>{currentStep}</CardBody>
</Card>
</StackItem>
</>
)}
</Stack>
</>
);
}
Example #11
Source File: ClipboardRenderer.tsx From DittoPlusPlus with MIT License | 4 votes |
ClipboardRenderer = (props: PluginTypes.RenderProps) => {
const [clipItems, updateClipItems] = useState<ClipItemDoc[]>([]);
const [selectedIndex, updateSelectedIndex] = useState(0);
const [imagesDir, updateImagesDir] = useState<string>('');
const [searchText, updateSearchText] = useState('');
const {pluginProcess} = props;
const searchBarRef = useRef<HTMLInputElement>(null);
const clipsListRef = useRef<HTMLDivElement>(null);
const resetClips = () => {
pluginProcess.send(Messages.GetAllClipItems, undefined, (err, clips) => {
if (!err) {
updateClipItems([...clips]);
}
});
};
const hideWindow = () => {
setImmediate(() => {
ipcRenderer.send(GlobalEvents.HideWindow);
});
};
const onKeyPress = (event: KeyboardEvent) => {
const {keyCode} = event;
/* disable scrolling by arrow keys */
if ([38, 40].includes(keyCode)) {
event.preventDefault();
}
if (clipsListRef.current) {
const {clipItemDimensions, searchBarDimensions} = dimensions;
const clipRowHeight =
clipItemDimensions.heightPx +
clipItemDimensions.paddingTopPx +
clipItemDimensions.paddingBottomPx;
const searchBarHeight =
searchBarDimensions.heightPx +
searchBarDimensions.paddingTopPx +
searchBarDimensions.paddingBottomPx;
const viewHeight = clipsListRef.current.offsetHeight - searchBarHeight;
const itemsVisibleN = Math.floor(viewHeight / clipRowHeight);
const itemsScrolled = Math.floor(
clipsListRef.current.scrollTop / clipRowHeight,
);
const isItemInViewPort = inRange(
selectedIndex,
itemsScrolled,
itemsVisibleN + itemsScrolled + 1,
);
/* up key */
if (keyCode === 38) {
if (isItemInViewPort) {
clipsListRef.current.scrollBy({
top: -clipRowHeight,
});
} else {
clipsListRef.current.scrollTop = (selectedIndex - 2) * clipRowHeight;
}
updateSelectedIndex((prevSelectedIndex) =>
clamp(prevSelectedIndex - 1, 0, clipItems.length - 1),
);
}
/* down key */
if (keyCode === 40) {
if (selectedIndex >= itemsVisibleN - 1 && isItemInViewPort) {
clipsListRef.current.scrollBy({top: clipRowHeight});
} else if (clipsListRef.current.scrollTop) {
clipsListRef.current.scrollTop = selectedIndex * clipRowHeight;
}
updateSelectedIndex((prevSelectedIndex) =>
clamp(prevSelectedIndex + 1, 0, clipItems.length - 1),
);
}
}
/* escape */
if (keyCode === 27) {
if (searchText) {
resetClips();
} else {
hideWindow();
}
handleSearchUpdate('');
}
/* enter key */
if (keyCode === 13) {
handleClipItemSelected(clipItems[selectedIndex]);
}
/* key is alphanumeric */
if (isAlphanumeric(keyCode)) {
updateSelectedIndex(0);
searchBarRef.current && searchBarRef.current.focus();
}
};
useEventListener('keydown', onKeyPress);
useEffect(() => {
resetClips();
pluginProcess.on(Events.NewClip, (clip: ClipItemDoc) => {
updateClipItems((prevClipItems) => [clip, ...prevClipItems]);
});
pluginProcess.on(Events.ClipsInitialized, (clips: ClipItemDoc[]) => {
updateClipItems([...clips]);
});
ipcRenderer.on(GlobalEvents.ShowWindow, () => {
if (process.platform === 'linux') {
setImmediate(() => {
searchBarRef.current && searchBarRef.current.blur();
});
}
});
ipcRenderer.on(GlobalEvents.HideWindow, () => {
handleSearchUpdate('');
});
pluginProcess.send(
Messages.GetImagesDir,
undefined,
(_err: any, _imagesDir: string) => {
updateImagesDir(_imagesDir);
},
);
}, []);
const handleClipItemSelected = (item: ClipItemDoc) => {
pluginProcess.send(Messages.ClipItemSelected, item, (err, res) => {
if (err) {
throw err;
}
if (res) {
updateClipItems([...res]);
}
});
handleSearchUpdate('');
hideWindow();
};
const onClickClipItem = (item: ClipItemDoc) => {
handleClipItemSelected(item);
};
const handleSearchUpdate = (text: string) => {
updateSearchText(text);
updateSelectedIndex(0);
clipsListRef.current && (clipsListRef.current.scrollTop = 0);
if (text === '') {
searchBarRef.current && searchBarRef.current.blur();
resetClips();
} else {
pluginProcess.send(Messages.SearchClips, text, (err, clips) => {
if (err) {
throw err;
}
updateClipItems([...clips]);
});
}
};
const onSearchTextChanged = (
event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
) => {
const query = event.target.value;
handleSearchUpdate(query);
};
const getClipItemVariant = (index: number): ClipItemVariants => {
if (index === selectedIndex) {
return 'selected';
}
if (index % 2 === 0) {
return 'dark';
} else {
return 'light';
}
};
return (
<StyledContainer>
<SimpleBar
style={SimpleBarStyles}
scrollableNodeProps={{ref: clipsListRef}}
>
{clipItems.map((item, index) => (
<ClipItem
key={`${index}_clipItem`}
clipItem={item}
imagesDir={imagesDir}
searchText={searchText}
variant={getClipItemVariant(index)}
onClick={() => onClickClipItem(item)}
/>
))}
</SimpleBar>
<SearchBar
id="clipboard-searchbar"
placeholder="search"
onChange={onSearchTextChanged}
value={searchText}
ref={searchBarRef}
/>
</StyledContainer>
);
}
Example #12
Source File: EditWaypointForm.tsx From project-tauntaun with GNU Lesser General Public License v3.0 | 4 votes |
export function EditWaypointForm(props: EditWaypointFormProps) {
const { selectWaypoint } = SelectionStateContainer.useContainer();
const { group, pointIndex } = props;
const point = group.points[pointIndex];
const movingPoint = point as MovingPoint;
const [alt, setAlt] = useState(point.alt);
const [altType, setAltType] = useState(movingPoint ? movingPoint.alt_type : 0);
const [type, setType] = useState(point.type);
const [name, setName] = useState(point.name);
const [speed, setSpeed] = useState(point.speed);
const [action, setAction] = useState(point.action);
const [currentPointIndex, setCurrentPointIndex] = useState(pointIndex);
const [useImperial, setImperial] = useState(true);
if (pointIndex !== currentPointIndex) {
setAlt(point.alt);
setType(point.type);
setName(point.name);
setSpeed(point.speed);
setAction(point.action);
setCurrentPointIndex(pointIndex);
if (movingPoint) {
setAltType(movingPoint.alt_type);
}
}
const actionsOptions = Object.keys(PointAction).map((key, value) => ({
value: key,
label: Object.values(PointAction)[value]
}));
const saveWaypointOnClick = () => {
// TODO validate
gameService.sendRouteModify(group, point.position, {
...point,
alt: alt,
alt_type: altType,
type: type,
name: name,
speed: speed,
action: action
} as MovingPoint);
selectWaypoint(undefined);
};
const onActionChange = (event: any) => {
setType(PointAction[event.target.value]);
setAction(event.target.value);
};
const closeOnClick = () => selectWaypoint(undefined);
const onUnitsSystemChange = () => {
setImperial(!useImperial);
};
const onAltTypeChange = (event: React.ChangeEvent<HTMLInputElement>) =>
setAltType(event.target.checked ? AltType.BARO : AltType.RADIO);
const onWpNumberUp = () => {
selectWaypoint(clamp(currentPointIndex + 1, 0, group.points.length - 1));
};
const onWpNumberDown = () => {
selectWaypoint(clamp(currentPointIndex - 1, 0, group.points.length - 1));
};
const onSetTargetClicked = () => {
setAltType(AltType.RADIO);
setAlt(0);
};
const GraySwitch = withStyles({
switchBase: {
color: 'rgba(49, 107, 170, 1)',
$track: {
backgroundColor: 'rgba(0,0,0,0.38)'
}
},
checked: {},
track: {}
})(Switch);
return (
<section className="EditWaypointContainer">
<header>
<h2>Group name: {group.name}</h2>
</header>
<div>
<div className="WaypointNumber">
<div>
<span>Waypoint</span>
<p>{currentPointIndex}</p>
</div>
<div className="WaypointButtonContainer">
<button onClick={onWpNumberDown}>Prev</button>
<button onClick={onWpNumberUp}>Next</button>
</div>
</div>
</div>
<div className="WaypointAltitude">
<span className="WaypointAltitudeTitle">Altitude</span>
<input
type="text"
pattern="[0-100000]"
value={useImperial ? alt * c_MeterToFeet : alt}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setAlt(useImperial ? +event.target.value / c_MeterToFeet : +event.target.value);
}}
/>
<div className="Toggle">
<span>Meters</span>
<FormControlLabel
control={
<GraySwitch
checked={useImperial}
onChange={onUnitsSystemChange}
name="Use Imperial"
size="small"
color="default"
/>
}
label=""
/>
<span>Feet</span>
</div>
{altType && (
<div className="Toggle">
<span>AGL</span>
<FormControlLabel
control={
<GraySwitch
checked={altType === AltType.BARO}
onChange={onAltTypeChange}
name="Use Imperial"
size="small"
color="default"
/>
}
label=""
/>
<span>MSL</span>
</div>
)}
</div>
<button onClick={onSetTargetClicked}>Set as ground target</button>
<div className="fieldContainer">
<span>Name</span>
<input
type="text"
value={name}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value);
}}
/>
</div>
<div className="fieldContainer">
<span>Speed</span>
<input
type="text"
pattern="[0-2]{0,1}[0-9]{1,3}[\.,][0-9]+"
value={speed}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setSpeed(+event.target.value);
}}
/>
</div>
<div className="fieldContainer">
<span>Action</span>
<Select onChange={onActionChange} value={action}>
{actionsOptions.map((option, i) => (
<MenuItem key={`actionsOptions${i}`} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
</div>
<div>
<button onClick={saveWaypointOnClick}>Save waypoint</button>
<button onClick={closeOnClick}>Close</button>
</div>
</section>
);
}
Example #13
Source File: setupTemplateProxy.ts From next-core with GNU General Public License v3.0 | 4 votes |
export function setupTemplateProxy(
proxyContext: Partial<ProxyContext>,
ref: string,
slots: SlotsConfOfBricks
): RuntimeBrickConfOfTplSymbols {
const computedPropsFromProxy: Record<string, unknown> = {};
let refForProxy: RefForProxy;
const {
reversedProxies,
templateProperties,
externalSlots,
templateContextId,
proxyBrick,
} = proxyContext;
if (ref && reversedProxies) {
refForProxy = {};
proxyBrick.proxyRefs.set(ref, refForProxy);
// Reversed proxies are used for expand storyboard before rendering page.
if (reversedProxies.properties.has(ref)) {
Object.assign(
computedPropsFromProxy,
Object.fromEntries(
reversedProxies.properties
.get(ref)
.flatMap((propRef) => {
// `propValue` is computed.
const propValue = templateProperties?.[propRef.$$reversedRef];
if (isTransformableProperty(propRef)) {
return Object.entries(
preprocessTransformProperties(
{
[propRef.$$reversedRef]: propValue,
},
propRef.refTransform
)
);
}
if (isBasicProperty(propRef)) {
return [[propRef.refProperty, propValue]];
}
// Ignore Variable properties.
// And mergeable properties are processed later.
return [];
})
.filter((propRef) => propRef[1] !== undefined)
)
);
// Brick properties can be merged multiple times.
if (reversedProxies.mergeBases.has(ref)) {
Object.assign(
computedPropsFromProxy,
Object.fromEntries(
Array.from(reversedProxies.mergeBases.get(ref).entries())
.map(([mergeProperty, mergeBase]) => [
mergeProperty,
propertyMergeAll(mergeBase, templateProperties ?? {}),
])
.filter((item) => item[1] !== undefined)
)
);
}
}
// Use an approach like template-literal's quasis:
// `quasi0${0}quais1${1}quasi2...`
// Every quasi (indexed by `refPosition`) can be slotted with multiple bricks.
const quasisMap = new Map<string, BrickConf[][]>();
if (reversedProxies.slots.has(ref)) {
for (const item of reversedProxies.slots.get(ref)) {
if (!quasisMap.has(item.refSlot)) {
const quasis: BrickConf[][] = [];
// The size of quasis should be the existed slotted bricks' size plus one.
const size = hasOwnProperty(slots, item.refSlot)
? slots[item.refSlot].bricks.length + 1
: 1;
for (let i = 0; i < size; i += 1) {
quasis.push([]);
}
quasisMap.set(item.refSlot, quasis);
}
const expandableSlot = quasisMap.get(item.refSlot);
const refPosition = item.refPosition ?? -1;
expandableSlot[
clamp(
refPosition < 0 ? expandableSlot.length + refPosition : refPosition,
0,
expandableSlot.length - 1
)
].push(...(externalSlots?.[item.$$reversedRef]?.bricks ?? []));
}
}
for (const [slotName, quasis] of quasisMap.entries()) {
if (!hasOwnProperty(slots, slotName)) {
slots[slotName] = {
type: "bricks",
bricks: [],
};
}
const slotConf = slots[slotName];
slotConf.bricks = quasis.flatMap((bricks, index) =>
index < slotConf.bricks.length
? bricks.concat(slotConf.bricks[index])
: bricks
);
if (slotConf.bricks.length === 0) {
delete slots[slotName];
}
}
}
return {
[symbolForComputedPropsFromProxy]: computedPropsFromProxy,
[symbolForRefForProxy]: refForProxy,
[symbolForTplContextId]: templateContextId,
};
}
Example #14
Source File: DropZone.tsx From next-core with GNU General Public License v3.0 | 4 votes |
export function DropZone({
nodeUid,
isRoot,
separateCanvas,
isPortalCanvas,
independentPortalCanvas,
canvasIndex,
mountPoint,
fullscreen,
delegatedContext,
dropZoneStyle,
dropZoneBodyStyle,
slotContentLayout,
showOutlineIfEmpty,
hiddenWrapper = true,
emptyClassName,
}: DropZoneProps): React.ReactElement {
const dropZoneBody = React.useRef<HTMLDivElement>();
const [dropPositionCursor, setDropPositionCursor] =
React.useState<DropPositionCursor>(null);
const dropPositionCursorRef = React.useRef<DropPositionCursor>();
const contextMenuStatus = useBuilderContextMenuStatus();
const manager = useBuilderDataManager();
const { nodes, edges, wrapperNode } = useBuilderData();
const isWrapper = hiddenWrapper && isRoot && !isEmpty(wrapperNode);
const node = useBuilderNode({ nodeUid, isRoot, isWrapper });
const groupedChildNodes = useBuilderGroupedChildNodes({
nodeUid,
isRoot,
doNotExpandTemplates: isWrapper,
isWrapper,
});
const isGeneralizedPortalCanvas = independentPortalCanvas
? canvasIndex > 0
: isPortalCanvas;
const hasTabs = separateCanvas || independentPortalCanvas;
const canDrop = useCanDrop();
const refinedSlotContentLayout =
slotContentLayout ?? EditorSlotContentLayout.BLOCK;
const selfChildNodes = React.useMemo(
() =>
groupedChildNodes.find((group) => group.mountPoint === mountPoint)
?.childNodes ?? [],
[groupedChildNodes, mountPoint]
);
const canvasList = useCanvasList(selfChildNodes);
const selfChildNodesInCurrentCanvas = React.useMemo(
() =>
separateCanvas
? selfChildNodes.filter((child) =>
Boolean(Number(Boolean(isPortalCanvas)) ^ Number(!child.portal))
)
: independentPortalCanvas
? canvasList[clamp(canvasIndex ?? 0, 0, canvasList.length - 1)]
: selfChildNodes,
[
canvasIndex,
independentPortalCanvas,
isPortalCanvas,
selfChildNodes,
canvasList,
separateCanvas,
]
);
const canvasSettings = React.useMemo(
() =>
selfChildNodesInCurrentCanvas[0]?.$$parsedProperties
._canvas_ as BuilderCanvasSettings,
[selfChildNodesInCurrentCanvas]
);
const getDroppingIndexInFullCanvas = React.useCallback(
(droppingIndexInCurrentCanvas: number) => {
if (!hasTabs) {
return droppingIndexInCurrentCanvas;
}
if (selfChildNodesInCurrentCanvas.length > 0) {
const cursorNode =
selfChildNodesInCurrentCanvas[
droppingIndexInCurrentCanvas === 0
? 0
: droppingIndexInCurrentCanvas - 1
];
return (
selfChildNodes.findIndex((child) => child === cursorNode) +
(droppingIndexInCurrentCanvas === 0 ? 0 : 1)
);
}
return isGeneralizedPortalCanvas ? selfChildNodes.length : 0;
},
[
hasTabs,
selfChildNodesInCurrentCanvas,
isGeneralizedPortalCanvas,
selfChildNodes,
]
);
const getDroppingContext = React.useCallback(() => {
if (delegatedContext) {
const siblingGroups = getBuilderGroupedChildNodes({
nodeUid: delegatedContext.templateUid,
nodes,
edges,
doNotExpandTemplates: true,
});
return {
droppingParentUid: delegatedContext.templateUid,
droppingParentInstanceId: nodes.find(
(item) => item.$$uid === delegatedContext.templateUid
).instanceId,
droppingMountPoint: delegatedContext.templateMountPoint,
droppingChildNodes:
siblingGroups.find(
(group) => group.mountPoint === delegatedContext.templateMountPoint
)?.childNodes ?? [],
droppingSiblingGroups: siblingGroups,
};
}
return {
droppingParentUid: node.$$uid,
droppingParentInstanceId: isWrapper
? wrapperNode.instanceId
: node.instanceId,
droppingMountPoint: mountPoint,
droppingChildNodes: selfChildNodes,
droppingSiblingGroups: groupedChildNodes,
};
}, [
delegatedContext,
edges,
groupedChildNodes,
mountPoint,
node,
nodes,
selfChildNodes,
isWrapper,
wrapperNode,
]);
const [{ isDraggingOverCurrent }, dropRef] = useDrop({
accept: [
BuilderDataTransferType.NODE_TO_ADD,
BuilderDataTransferType.NODE_TO_MOVE,
BuilderDataTransferType.SNIPPET_TO_APPLY,
],
canDrop: (
item: DragObjectWithType &
(
| BuilderDataTransferPayloadOfNodeToAdd
| BuilderDataTransferPayloadOfNodeToMove
)
) =>
independentPortalCanvas && isGeneralizedPortalCanvas
? selfChildNodesInCurrentCanvas.length === 0
: item.type === BuilderDataTransferType.NODE_TO_ADD ||
item.type === BuilderDataTransferType.SNIPPET_TO_APPLY ||
isRoot ||
canDrop(
(item as BuilderDataTransferPayloadOfNodeToMove).nodeUid,
nodeUid
),
collect: (monitor) => ({
isDraggingOverCurrent:
monitor.isOver({ shallow: true }) && monitor.canDrop(),
}),
hover: (item, monitor) => {
if (monitor.isOver({ shallow: true }) && monitor.canDrop()) {
const { x, y } = monitor.getClientOffset();
dropPositionCursorRef.current = getDropPosition(
x,
y,
dropZoneBody.current.parentElement,
dropZoneBody.current
);
setDropPositionCursor(dropPositionCursorRef.current);
}
},
drop: (item, monitor) => {
if (!monitor.didDrop()) {
const { type, ...data } = item;
processDrop({
type: type as BuilderDataTransferType,
data,
droppingIndex: getDroppingIndexInFullCanvas(
dropPositionCursorRef.current.index
),
isPortalCanvas: isGeneralizedPortalCanvas,
manager,
...getDroppingContext(),
});
}
},
});
React.useEffect(() => {
manager.updateDroppingStatus(
delegatedContext ? delegatedContext.templateUid : node.$$uid,
delegatedContext ? delegatedContext.templateMountPoint : mountPoint,
isDraggingOverCurrent
);
}, [isDraggingOverCurrent, mountPoint, manager, delegatedContext, node]);
const droppable =
!!delegatedContext ||
isWrapper ||
!(node.$$isExpandableTemplate || node.$$isTemplateInternalNode);
const dropZoneRef = React.useRef<HTMLElement>();
const dropZoneRefCallback = React.useCallback(
(element: HTMLElement) => {
dropZoneRef.current = element;
if (droppable) {
dropRef(element);
}
},
[dropRef, droppable]
);
const handleContextMenu = React.useCallback(
(event: React.MouseEvent) => {
// `event.stopPropagation()` not working across bricks.
if (
!isGeneralizedPortalCanvas &&
isCurrentTargetByClassName(
event.target as HTMLElement,
dropZoneRef.current
)
) {
event.preventDefault();
manager.contextMenuChange({
active: true,
node,
x: event.clientX,
y: event.clientY,
});
}
},
[isGeneralizedPortalCanvas, manager, node]
);
return (
<div
ref={dropZoneRefCallback}
className={classNames(
styles.dropZone,
isRoot
? classNames(
styles.isRoot,
canvasSettings?.mode &&
String(canvasSettings.mode)
.split(/\s+/g)
.map((mode) => styles[`mode-${mode}`]),
{
[styles.fullscreen]: fullscreen,
[styles.hasTabs]: hasTabs,
}
)
: styles.isSlot,
{
[styles.isPortalCanvas]: isGeneralizedPortalCanvas,
[styles.dropping]: isDraggingOverCurrent,
[styles.active]:
isRoot &&
contextMenuStatus.active &&
contextMenuStatus.node.$$uid === node.$$uid,
[styles.showOutlineIfEmpty]:
!isRoot && showOutlineIfEmpty && selfChildNodes.length === 0,
[styles.slotContentLayoutBlock]:
refinedSlotContentLayout === EditorSlotContentLayout.BLOCK,
[styles.slotContentLayoutInline]:
refinedSlotContentLayout === EditorSlotContentLayout.INLINE,
[styles.slotContentLayoutGrid]:
refinedSlotContentLayout === EditorSlotContentLayout.GRID,
}
)}
style={dropZoneStyle}
onContextMenu={isRoot ? handleContextMenu : null}
>
<div
ref={dropZoneBody}
className={classNames(
styles.dropZoneBody,
selfChildNodesInCurrentCanvas.length === 0 && emptyClassName
)}
data-slot-id={mountPoint}
style={dropZoneBodyStyle}
>
{selfChildNodesInCurrentCanvas.map((child) => (
<EditorBrickAsComponent
key={child.$$uid}
node={child}
slotContentLayout={refinedSlotContentLayout}
/>
))}
</div>
{
<div
className={classNames(
styles.dropCursor,
dropPositionCursor?.isVertical
? styles.dropCursorVertical
: styles.dropCursorHorizontal
)}
style={{
top: dropPositionCursor?.y,
left: dropPositionCursor?.x,
height: dropPositionCursor?.height,
}}
></div>
}
</div>
);
}
Example #15
Source File: expandTemplateEdges.ts From next-core with GNU General Public License v3.0 | 4 votes |
export function expandTemplateEdges({
nodes,
// Here expanded edges should be excluded.
edges,
rootId,
}: BuilderCanvasData): BuilderRuntimeEdge[] {
const newEdges = edges.slice();
const reorderedEdgesMap = new WeakMap<BuilderRuntimeEdge, number>();
const walk = (
uid: number,
callback: (
node: BuilderRuntimeNode,
childEdges: BuilderRuntimeEdge[]
) => void
): void => {
const node = nodes.find((item) => item.$$uid === uid);
const childEdges = sortBy(
edges.filter((edge) => edge.parent === node.$$uid),
(edge) => edge.sort
);
callback(node, childEdges);
for (const edge of childEdges) {
walk(edge.child, callback);
}
};
const expandEdgesThroughTemplateChain = (
node: BuilderRuntimeNode,
mountPoint: string,
childUid: number
): void => {
let proxySlotConf: CustomTemplateProxySlot;
let delegateToParentUid: number;
if (
node.$$isExpandableTemplate &&
node.$$templateProxy?.slots &&
hasOwnProperty(node.$$templateProxy.slots, mountPoint) &&
(proxySlotConf = node.$$templateProxy.slots[mountPoint]) &&
(delegateToParentUid = node.$$templateRefToUid.get(proxySlotConf.ref))
) {
const nextNode = nodes.find((n) => n.$$uid === delegateToParentUid);
const nextMountPoint = proxySlotConf.refSlot;
if (nextNode.$$isExpandableTemplate) {
expandEdgesThroughTemplateChain(nextNode, nextMountPoint, childUid);
} else {
const expandedEdge: BuilderRuntimeEdge = {
child: childUid,
parent: delegateToParentUid,
mountPoint: nextMountPoint,
sort: undefined,
$$isTemplateExpanded: true,
};
const siblingEdges = sortBy(
newEdges.filter((edge) => edge.parent === delegateToParentUid),
(edge) => reorderedEdgesMap.get(edge) ?? edge.sort
);
const internalEdges = siblingEdges.filter(
(edge) => edge.$$isTemplateInternal
);
// For more details about refPosition implementation detail,
// see `packages/brick-kit/src/core/CustomTemplates/expandCustomTemplate.ts`.
const refPosition = proxySlotConf.refPosition ?? -1;
const clampedRefPosition = clamp(
refPosition < 0
? internalEdges.length + 1 + refPosition
: refPosition,
0,
internalEdges.length
);
siblingEdges.splice(
clampedRefPosition < internalEdges.length
? siblingEdges.findIndex(
(edge) => edge === internalEdges[clampedRefPosition]
)
: siblingEdges.length,
0,
expandedEdge
);
siblingEdges.forEach((edge, index) => {
reorderedEdgesMap.set(edge, index);
});
newEdges.push(expandedEdge);
}
}
};
walk(rootId, (node, childEdges) => {
if (!node.$$isExpandableTemplate) {
return;
}
for (const childEdge of childEdges) {
// Recursively expand templates.
expandEdgesThroughTemplateChain(
node,
childEdge.mountPoint,
childEdge.child
);
}
});
if (newEdges.length > edges.length) {
return newEdges.map((edge) => {
const sort = reorderedEdgesMap.get(edge);
return sort === undefined
? edge
: {
...edge,
sort,
};
});
}
return edges;
}