import * as T from "../types" ; import { set , del , firstKey } from "../helpers/immutable-map" ; import { isComponentLayer , findLayerById , canHaveChildren , updateLayer , getComponentOrThrow , findLayerByIdWithParent , } from "../layerUtils" ; import uuid from "uuid/v4" ; import { makeLayer } from "../factories" ; import { uiStateComponentOrThrow } from "../refsUtil" ; import { assertUnreachable , LayerStyle } from "@phijs/shared" ; import { decrement , lengthToString , increment , parseLength , Unit , } from "../lengthUtils" ; function goToFirstComponentOrDefault ( components ) { return components . size === 0 ? { type : "typography" } : { type : "component" , componentId : firstKey ( components ) , isEditing : false , layerEditorMode : "html" , isCodeVisible : false , } ; } function replaceComponent ( refs , componentId , update ) { const component = getComponentOrThrow ( componentId , refs ) ; return { ... refs , components : set ( refs . components , componentId , { ... component , ... update ( component ) , } ) , } ; } function replaceLayer < TLayer extends T . Layer > ( refs , componentId , layerId , update ) { return replaceComponent ( refs , componentId , ( component ) => { if ( component . layout == null ) { throw new Error ( "Cannot replace layer if component.layout is null" ) ; } const layer = findLayerById ( component . layout , layerId ) ; if ( layer == null ) { throw new Error ( "Layer not found" ) ; } return { ... component , layout : updateLayer ( component . layout , { ... layer , ... update ( layer as TLayer ) , } ) , } ; } ) ; } function replaceLayerStyle ( refs , uiState , update ) { if ( uiState . layerId == null ) { throw new Error ( "Cannot replace layer style if not layer selected" ) ; } return replaceLayer ( refs , uiState . componentId , uiState . layerId , ( layer ) => { if ( uiState . mediaQuery == null ) { return { style : { ... layer . style , ... update ( layer . style ) } , } ; } return { mediaQueries : layer . mediaQueries . map ( ( mq ) => mq . id === uiState . mediaQuery ? { ... mq , style : { ... mq . style , ... update ( mq . style ) } , } : mq ) , } ; } ) ; } function visitLayer ( root , visitor ) { switch ( root . type ) { case "container" : case "link" : return { ... visitor ( root ) , children : root . children . map ( ( child ) => visitLayer ( child , visitor ) ) , } ; default : return visitor ( root ) ; } } export function addComponentPropHandler ( action , refs ) { return replaceComponent ( refs , action . componentId , ( c ) => ( { props : [ ... c . props , { name : action . prop , type : "text" } ] , } ) ) ; } export function editComponentPropHandler ( action , refs ) { return { ... refs , components : new Map ( Array . from ( refs . components . entries ( ) ) . map ( ( [ componentId , component ] ) => { if ( componentId === action . componentId ) { return [ componentId , renamePropertyFromComponent ( component , action . oldProp , action . newProp ) , ] ; } return [ componentId , renamePropertyFromComponentLayer ( component , action . componentId , action . oldProp , action . newProp ) , ] ; } ) ) , } ; } function renamePropertyFromComponent ( component , oldProp , newProp ) { const newComponent = { ... component , props : component . props . map ( ( prop ) => prop . name === oldProp ? { name : newProp , type : prop . type } : prop ) , } ; if ( component . layout ) { newComponent . layout = visitLayer ( component . layout , ( layer ) => renameAllBindingsThatUseProp ( layer , oldProp , newProp ) ) ; } return newComponent ; } function renameAllBindingsThatUseProp < T extends T . Layer > ( layer , oldProp , newProp ) { if ( Object . values ( layer . bindings ) . some ( ( binding ) => binding . propName === oldProp ) ) { return { ... layer , bindings : Object . entries ( layer . bindings ) . reduce ( ( newBindings , [ prop , binding ] ) => { if ( binding . propName === oldProp ) { newBindings [ newProp ] = binding ; } else { newBindings [ prop ] = binding ; } return newBindings ; } , { } as T . Bindings ) , } ; } return layer ; } function renamePropertyFromComponentLayer ( component , childComponentId , oldProp , newProp ) { if ( component . layout == null ) { return component ; } return { ... component , layout : visitLayer ( component . layout , ( layer ) => { if ( isComponentLayer ( layer ) && layer . componentId === childComponentId ) { const props = { ... layer . props , } ; const propValue = props [ oldProp ] ; delete props [ oldProp ] ; props [ newProp ] = propValue ; const bindings = { ... layer . bindings , } ; const bindingValue = bindings [ oldProp ] ; delete bindings [ oldProp ] ; bindings [ newProp ] = bindingValue ; return { ... layer , props , bindings , } ; } return layer ; } ) , } ; } export function deleteComponentPropHandler ( action , refs ) { return { ... refs , components : new Map < string , T . Component > ( Array . from ( refs . components . entries ( ) ) . map ( ( [ componentId , component ] ) => { if ( componentId === action . componentId ) { return [ componentId , deletePropertyFromComponent ( component , action . prop ) , ] ; } return [ componentId , deletePropertyFromComponentLayer ( component , action . componentId , action . prop ) , ] ; } ) ) , } ; } function deletePropertyFromComponent ( component , propName ) { const newComponent = { ... component , props : component . props . filter ( ( prop ) => prop . name !== propName ) , } ; if ( component . layout ) { newComponent . layout = visitLayer ( component . layout , ( layer ) => deleteAllBindingsThatUseProp ( layer , propName ) ) ; } return newComponent ; } function deletePropertyFromComponentLayer ( component , childComponentId , propName ) { if ( component . layout == null ) { return component ; } return { ... component , layout : visitLayer ( component . layout , ( layer ) => { if ( isComponentLayer ( layer ) && layer . componentId === childComponentId ) { const { [ propName ] : unusedProp , ... props } = layer . props ; const { [ propName ] : unusedBinding , ... bindings } = layer . bindings ; return { ... layer , props , bindings , } ; } return layer ; } ) , } ; } function deleteAllBindingsThatUseProp < T extends T . Layer > ( layer , propName ) { if ( Object . values ( layer . bindings ) . some ( ( binding ) => binding . propName === propName ) ) { return { ... layer , bindings : Object . entries ( layer . bindings ) . reduce ( ( newBindings , [ prop , binding ] ) => { if ( binding . propName !== propName ) { newBindings [ prop ] = binding ; } return newBindings ; } , { } as T . Bindings ) , } ; } return layer ; } export function renameComponentHandler ( action , refs ) { return replaceComponent ( refs , action . componentId , ( c ) => ( { name : action . name , } ) ) ; } export function deleteComponentHandler ( action , refs ) { const components = del ( refs . components , action . componentId ) ; return { ... refs , components , uiState : goToFirstComponentOrDefault ( components ) , } ; } export function addComponentExampleHandler ( action , refs ) { return replaceComponent ( refs , action . componentId , ( c ) => ( { examples : c . examples . concat ( [ { props : { } , name : action . name , id : uuid ( ) } ] ) , } ) ) ; } export function deleteComponentExampleHandler ( action , refs ) { return replaceComponent ( refs , action . componentId , ( c ) => ( { examples : c . examples . filter ( ( e ) => e . id !== action . id ) , } ) ) ; } export function updateComponentExamplePropHandler ( action , refs ) { return replaceComponent ( refs , action . componentId , ( c ) => ( { examples : c . examples . map ( ( example ) => example . id === action . exampleId ? { ... example , props : { ... example . props , [ action . prop ] : action . value , } , } : example ) , } ) ) ; } function addLayer ( root , selectedLayerId , newLayer ) { if ( ! root ) { return newLayer ; } if ( ! selectedLayerId ) { return ; } const { layer , parent } = findLayerByIdWithParent ( root , selectedLayerId ) ; if ( ! layer ) { throw new Error ( `Layer with id ${ selectedLayerId } not found in root` ) ; } if ( canHaveChildren ( layer ) ) { return updateLayer ( root , { ... layer , children : [ ... layer . children ] . concat ( newLayer ) , } ) ; } if ( parent ) { return updateLayer ( root , { ... parent , children : [ ... parent . children ] . concat ( newLayer ) , } ) ; } } export function addLayerActionHandler ( action , refs ) { const result = replaceComponent ( refs , action . componentId , ( component ) => { const newLayer = makeLayer ( action . layerId , action . layerType , component . layout , refs , action . layerComponentId ) ; return { ... component , layout : addLayer ( component . layout , action . parentLayerId , newLayer ) , } ; } ) ; return { ... result , uiState : { ... uiStateComponentOrThrow ( refs ) , layerId : action . layerId , } , } ; } function deleteLayer ( root , layerIdToDelete ) { if ( root . id === layerIdToDelete ) { return undefined ; } if ( ! canHaveChildren ( root ) ) { return root ; } return { ... root , children : root . children . map ( ( child ) => deleteLayer ( child , layerIdToDelete ) ) . filter ( ( x ) => x !== undefined ) as T . Layer [ ] , } ; } function deleteComponentLayer ( refs , componentId , layerId ) { const result = replaceComponent ( refs , componentId , ( component ) => { return { ... component , layout : deleteLayer ( component . layout ! , layerId ) , } ; } ) ; return { ... result , uiState : { ... uiStateComponentOrThrow ( refs ) , layerId : undefined , } , } ; } function deleteLayerActionHandler ( action , refs ) { return deleteComponentLayer ( refs , action . componentId , action . layerId ) ; } function selectLayerHandler ( action , refs ) { return { ... refs , uiState : { ... uiStateComponentOrThrow ( refs ) , layerId : action . layerId , } , } ; } function renameLayerHandler ( action , refs ) { const result = replaceComponent ( refs , action . componentId , ( component ) => { const layer = findLayerById ( component . layout ! , action . layerId ) ; if ( ! layer ) { throw new Error ( `Layer with id ${ action . layerId } not found in root` ) ; } return { ... component , layout : updateLayer ( component . layout , { ... layer , name : action . name , } ) , } ; } ) ; const uiState = uiStateComponentOrThrow ( refs ) ; return { ... result , uiState : { ... uiState , componentId : action . componentId , layerId : action . layerId , } , } ; } function moveLayer ( root , layerId , parentId , position ) { const layerToMove = findLayerById ( root , layerId ) ; if ( layerToMove == null ) { throw new Error ( `Tried to move layer with id ${ layerId } but it was not found in the tree` ) ; } const tmp = deleteLayer ( root , layerToMove . id ) ; if ( ! tmp ) { throw new Error ( "Temporary layer after delete should exist. If not, the root has been deleted" ) ; } return insertLayer ( tmp , layerToMove , parentId , position ) ; } function insertLayer ( root , toInsert , parentId , position ) { if ( canHaveChildren ( root ) ) { if ( root . id === parentId ) { return { ... root , children : root . children . slice ( 0 , position ) . concat ( [ toInsert ] ) . concat ( root . children . slice ( position ) ) , } ; } return { ... root , children : root . children . map ( ( child ) => insertLayer ( child , toInsert , parentId , position ) ) , } ; } if ( root . id === parentId ) { throw new Error ( "A layer can only be inserted inside a container" ) ; } return root ; } function moveLayerActionHandler ( action , refs ) { return replaceComponent ( refs , action . componentId , ( component ) => { return { ... component , layout : moveLayer ( component . layout ! , action . layerId , action . parentId , action . position ) , } ; } ) ; } function updateLayerPropHandler ( action , refs ) { return replaceLayer ( refs , action . componentId , action . layerId , ( layer ) => { return { props : { ... layer . props , [ action . name ] : action . value } , } ; } ) ; } function updateLayerTagHandler ( action , refs ) { return replaceLayer ( refs , action . componentId , action . layerId , ( layer ) => { if ( layer . type !== "text" ) { throw new Error ( "Replace tag value is only supported for text layer for now" ) ; } return { tag : action . tag , } ; } ) ; } function updateLayerBindingHandler ( action , refs ) { return replaceLayer ( refs , action . componentId , action . layerId , ( layer ) => { return { bindings : { ... layer . bindings , [ action . layerProp ] : { propName : action . componentProp } , } , } ; } ) ; } function deleteLayerBindingHandler ( action , refs ) { return replaceLayer ( refs , action . componentId , action . layerId , ( layer ) => { const { [ action . layerProp ] : unusedValue , ... newBindings } = layer . bindings ; return { bindings : newBindings , } ; } ) ; } function addComponentHandler ( action , refs ) { return { ... refs , components : set ( refs . components , action . componentId , { name : action . name , props : [ ] , examples : [ ] , } ) , uiState : { type : "component" , componentId : action . componentId , isEditing : true , layerEditorMode : "html" , isCodeVisible : false , } , } ; } function initProjectHandler ( action , refs ) { return { ... action . refs , uiState : goToFirstComponentOrDefault ( action . refs . components ) , } ; } function goToHandler ( action , refs ) { return { ... refs , uiState : action . to , } ; } function updateLayerStyleHandler ( action , refs ) { const uiState = uiStateComponentOrThrow ( refs ) ; if ( uiState . layerId == null ) { throw new Error ( "Expected uiState.layerId to be defined to update layer style." ) ; } return replaceLayer ( refs , uiState . componentId , uiState . layerId , ( layer ) => { if ( uiState . mediaQuery == null ) { return { style : { ... layer . style , ... action . style } , } ; } return { mediaQueries : layer . mediaQueries . map ( ( mq ) => mq . id === uiState . mediaQuery ? { ... mq , style : { ... mq . style , ... action . style } , } : mq ) , } ; } ) ; } function addMediaQueryHandler ( action , refs ) { const newRefs = replaceLayer ( refs , action . componentId , action . layerId , ( layer ) => { return { mediaQueries : [ ... layer . mediaQueries , { id : action . mediaQueryId , minWidth : { type : "ref" , id : action . breakpointId } , style : { ... layer . style } , } , ] , } ; } ) ; const uiState = uiStateComponentOrThrow ( newRefs ) ; return { ... newRefs , uiState : { ... uiState , mediaQuery : action . mediaQueryId , } , } ; } function updateRefHandler ( action , refs ) { return { ... refs , [ action . refType ] : set ( refs [ action . refType ] , action . refId , action . newRef ) , } ; } function deleteRefHandler ( action , refs ) { return { ... refs , [ action . refType ] : del ( refs [ action . refType ] as Map < string , any > , action . refId ) , } ; } function editComponentHandler ( action , refs ) { if ( refs . uiState . type !== "component" ) { throw new Error ( `uiState.type should be "component" to start editing it` ) ; } const component = getComponentOrThrow ( refs . uiState . componentId , refs ) ; return { ... refs , uiState : { ... refs . uiState , isEditing : true , layerId : component . layout ?. id , } , } ; } function stopEditComponentHandler ( action , refs ) { if ( refs . uiState . type !== "component" ) { throw new Error ( `uiState.type should be "component" to start editing it` ) ; } return { ... refs , uiState : { ... refs . uiState , layerId : undefined , isEditing : false , } , } ; } function setLayerEditorModeHandler ( action , refs ) { return { ... refs , uiState : { ... uiStateComponentOrThrow ( refs ) , layerEditorMode : action . mode , } , } ; } function hoverLayerHandler ( action , refs ) { return { ... refs , uiState : { ... uiStateComponentOrThrow ( refs ) , hoveredLayerId : action . layerId , } , } ; } function handleBackspaceShortcut ( refs ) { if ( refs . uiState . type === "component" && refs . uiState . layerId && refs . uiState . isEditing ) { return deleteComponentLayer ( refs , refs . uiState . componentId , refs . uiState . layerId ) ; } return refs ; } function moveChildPositionUp ( refs , componentId , layer , parent ) { const childIndex = parent . children . indexOf ( layer ) ; if ( childIndex === 0 ) { return refs ; } return replaceComponent ( refs , componentId , ( component ) => { return { layout : moveLayer ( component . layout ! , layer . id , parent . id , childIndex - 1 ) , } ; } ) ; } function moveChildPositionDown ( refs , componentId , layer , parent ) { const childIndex = parent . children . indexOf ( layer ) ; if ( childIndex === parent . children . length - 1 ) { return refs ; } return replaceComponent ( refs , componentId , ( component ) => { return { layout : moveLayer ( component . layout ! , layer . id , parent . id , childIndex + 1 ) , } ; } ) ; } function handleArrowShortcutForPosition ( refs , key ) { if ( refs . uiState . type !== "component" || refs . uiState . layerId == null || refs . uiState . isEditing === false ) { return refs ; } const component = getComponentOrThrow ( refs . uiState . componentId , refs ) ; if ( component . layout == null ) { return refs ; } const { layer , parent } = findLayerByIdWithParent ( component . layout , refs . uiState . layerId ) ; if ( layer == null || parent == null ) { return refs ; } if ( parent . type === "container" && parent . style . display !== "flex" && parent . style . display != null ) { return refs ; } if ( parent . type === "link" && parent . style . display !== "flex" ) { return refs ; } const dir = parent . style . flexDirection ; if ( ( dir === "column" && key === "ArrowUp" ) || ( dir === "row" && key === "ArrowLeft" ) || ( dir === "column-reverse" && key === "ArrowDown" ) || ( dir === "row-reverse" && key === "ArrowRight" ) ) { return moveChildPositionUp ( refs , refs . uiState . componentId , layer , parent ) ; } if ( ( dir === "column" && key === "ArrowDown" ) || ( dir === "row" && key === "ArrowRight" ) || ( dir === "column-reverse" && key === "ArrowUp" ) || ( dir === "row-reverse" && key === "ArrowLeft" ) ) { return moveChildPositionDown ( refs , refs . uiState . componentId , layer , parent ) ; } if ( ( dir === "column" && key === "ArrowLeft" ) || ( dir === "row" && key === "ArrowUp" ) || ( dir === "column-reverse" && key === "ArrowRight" ) || ( dir === "row-reverse" && key === "ArrowDown" ) ) { return replaceLayerStyle ( refs , refs . uiState , ( style ) => { return { alignSelf : style . alignSelf === "flex-end" ? "center" : "flex-start" , } ; } ) ; } if ( ( dir === "column" && key === "ArrowRight" ) || ( dir === "row" && key === "ArrowDown" ) || ( dir === "column-reverse" && key === "ArrowLeft" ) || ( dir === "row-reverse" && key === "ArrowUp" ) ) { return replaceLayerStyle ( refs , refs . uiState , ( style ) => { return { alignSelf : style . alignSelf === "flex-start" ? "center" : "flex-end" , } ; } ) ; } return refs ; } function handleArrowShortcutForSize ( refs , key ) { if ( refs . uiState . type !== "component" || refs . uiState . layerId == null || refs . uiState . isEditing === false ) { return refs ; } return replaceLayerStyle ( refs , refs . uiState , ( style ) => { const propertyName = key === "ArrowUp" || key === "ArrowDown" ? "height" : "width" ; const value = style [ propertyName ] ; const newValue = key === "ArrowDown" || key === "ArrowRight" ? increment ( value ) : decrement ( value , true ) ; return { [ propertyName ] : newValue ? lengthToString ( newValue ) : undefined , } ; } ) ; } function globalShortcutActionHandler ( action , refs ) { switch ( action . key ) { case "Backspace" : return handleBackspaceShortcut ( refs ) ; case "ArrowUp" : case "ArrowRight" : case "ArrowDown" : case "ArrowLeft" : return action . metaKey ? handleArrowShortcutForSize ( refs , action . key ) : handleArrowShortcutForPosition ( refs , action . key ) ; default : return refs ; } } function moveLayerUpOrDownHandler ( action , refs ) { const uiState = uiStateComponentOrThrow ( refs ) ; const component = getComponentOrThrow ( uiState . componentId , refs ) ; if ( component . layout == null || uiState . layerId == null ) { return refs ; } const { layer , parent } = findLayerByIdWithParent ( component . layout , uiState . layerId ) ; if ( layer == null ) { throw new Error ( "Layer not found" ) ; } if ( parent == null ) { throw new Error ( "Parent layer is needed to move child layer" ) ; } switch ( action . direction ) { case "up" : return moveChildPositionUp ( refs , uiState . componentId , layer , parent ) ; case "down" : return moveChildPositionDown ( refs , uiState . componentId , layer , parent ) ; } assertUnreachable ( action . direction ) ; } function calculateResizeOffsetX ( action ) { switch ( action . direction ) { case "right" : case "bottom-right" : case "top-right" : return action . offset . x ; case "left" : case "bottom-left" : case "top-left" : return - action . offset . x ; case "top" : case "bottom" : return 0 ; } } function calculateResizeOffsetY ( action ) { switch ( action . direction ) { case "bottom" : case "bottom-right" : case "bottom-left" : return action . offset . y ; case "top" : case "top-left" : case "top-right" : return - action . offset . y ; case "right" : case "left" : return 0 ; } } function resizeLayerStyleHandler ( action , refs ) { const uiState = uiStateComponentOrThrow ( refs ) ; if ( uiState . layerId == null ) { throw new Error ( `Expected uiState.layerId to be defined when resizing layer.` ) ; } return replaceLayerStyle ( refs , uiState , ( style ) => { const offsetX = calculateResizeOffsetX ( action ) ; const offsetY = calculateResizeOffsetY ( action ) ; const newStyle = { } ; if ( offsetX !== 0 ) { const lengthWidth = parseLength ( style . width , true ) ; newStyle . width = lengthToString ( lengthWidth ? { value : lengthWidth . value + offsetX , unit : lengthWidth . unit , } : { value : action . canvasSize . width + offsetX , unit : "px" as Unit , } ) ; } if ( offsetY !== 0 ) { const lengthHeight = parseLength ( style . height , true ) ; newStyle . height = lengthToString ( lengthHeight ? { value : lengthHeight . value + offsetY , unit : lengthHeight . unit , } : { value : action . canvasSize . height + offsetY , unit : "px" as Unit , } ) ; } return newStyle ; } ) ; } function resetHandler ( action , refs ) { return action . refs ; } function selectMediaQueryHandler ( action , refs ) { const uiState = uiStateComponentOrThrow ( refs ) ; return { ... refs , uiState : { ... uiState , mediaQuery : action . mediaQuery , } , } ; } function toggleCodeVisibilityHandler ( action , refs ) { const uiState = uiStateComponentOrThrow ( refs ) ; return { ... refs , uiState : { ... uiState , isCodeVisible : action . isVisible , } , } ; } export default function applyAction ( action , refs ) { switch ( action . type ) { case "goTo" : return goToHandler ( action , refs ) ; case "globalShortcutAction" : return globalShortcutActionHandler ( action , refs ) ; case "reset" : return resetHandler ( action , refs ) ; case "selectMediaQuery" : return selectMediaQueryHandler ( action , refs ) ; case "initProject" : return initProjectHandler ( action , refs ) ; case "addComponentProp" : return addComponentPropHandler ( action , refs ) ; case "editComponentProp" : return editComponentPropHandler ( action , refs ) ; case "deleteComponentProp" : return deleteComponentPropHandler ( action , refs ) ; case "renameComponent" : return renameComponentHandler ( action , refs ) ; case "addComponent" : return addComponentHandler ( action , refs ) ; case "deleteComponent" : return deleteComponentHandler ( action , refs ) ; case "addComponentExample" : return addComponentExampleHandler ( action , refs ) ; case "deleteComponentExample" : return deleteComponentExampleHandler ( action , refs ) ; case "updateComponentExampleProp" : return updateComponentExamplePropHandler ( action , refs ) ; case "addLayer" : return addLayerActionHandler ( action , refs ) ; case "deleteLayer" : return deleteLayerActionHandler ( action , refs ) ; case "selectLayer" : return selectLayerHandler ( action , refs ) ; case "renameLayer" : return renameLayerHandler ( action , refs ) ; case "moveLayer" : return moveLayerActionHandler ( action , refs ) ; case "moveLayerUpOrDown" : return moveLayerUpOrDownHandler ( action , refs ) ; case "updateLayerProp" : return updateLayerPropHandler ( action , refs ) ; case "updateLayerTag" : return updateLayerTagHandler ( action , refs ) ; case "updateLayerBinding" : return updateLayerBindingHandler ( action , refs ) ; case "deleteLayerBinding" : return deleteLayerBindingHandler ( action , refs ) ; case "updateLayerStyle" : return updateLayerStyleHandler ( action , refs ) ; case "resizeLayer" : return resizeLayerStyleHandler ( action , refs ) ; case "addMediaQuery" : return addMediaQueryHandler ( action , refs ) ; case "updateRef" : return updateRefHandler ( action , refs ) ; case "deleteRef" : return deleteRefHandler ( action , refs ) ; case "editComponent" : return editComponentHandler ( action , refs ) ; case "stopEditComponent" : return stopEditComponentHandler ( action , refs ) ; case "setLayerEditorMode" : return setLayerEditorModeHandler ( action , refs ) ; case "hoverLayer" : return hoverLayerHandler ( action , refs ) ; case "toggleCodeVisibility" : return toggleCodeVisibilityHandler ( action , refs ) ; } } type LayerVisitor = < T extends T . Layer > ( layer : T ) => T ;