Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@gravity-ui/page-constructor
Advanced tools
Page-constructor
is a library for rendering web pages or their parts based on JSON
data (support for YAML
format is to be added later).
When creating pages, component-based approach is used: a page is built using a set of ready-made blocks that can be placed in any order. Each block has a certain type and set of input data parameters.
For the format of input data and list of available blocks, see the documentation.
npm install @gravity-ui/page-constructor
Please note that to start using the package, your project must also have the following installed: @diplodoc/transform
, @gravity-ui/uikit
, react
. Check out the peerDependencies
section of package.json
for accurate information.
The page constructor is imported as a React component. To make sure it runs properly, wrap it in PageConstructorProvider
:
import {PageConstructor, PageConstructorProvider} from '@gravity-ui/page-constructor';
const Page: React.PropsWithChildren<PageProps> = ({content}) => (
<PageConstructorProvider>
<PageConstructor content={content} />
</PageConstructorProvider>
);
interface PageConstructorProps {
content: PageContent; // Blocks data in JSON format.
shouldRenderBlock?: ShouldRenderBlock; // A function that is invoked when rendering each block and lets you set conditions for its display.
custom?: Custom; // Custom blocks (see `Customization`).
renderMenu?: () => React.ReactNode; // A function that renders the page menu with navigation (we plan to add rendering for the default menu version).
navigation?: NavigationData; // Navigation data for using navigation component in JSON format
isBranded?: boolean; // If true, adds a footer that links to https://gravity-ui.com/. Try BrandFooter component for more customization.
}
interface PageConstructorProviderProps {
isMobile?: boolean; //A flag indicating that the code is executed in mobile mode.
locale?: LocaleContextProps; //Info about the language and domain (used when generating and formatting links).
location?: Location; //API of the browser or router history, the page URL.
analytics?: AnalyticsContextProps; // function to handle analytics event
ssrConfig?: SSR; //A flag indicating that the code is run on the server size.
theme?: 'light' | 'dark'; //Theme to render the page with.
mapsContext?: MapsContextType; //Params for map: apikey, type, scriptSrc, nonce
}
export interface PageContent extends Animatable {
blocks: Block[];
menu?: Menu;
background?: MediaProps;
}
interface Custom {
blocks?: CustomItems;
subBlocks?: CustomItems;
headers?: CustomItems;
loadable?: LoadableConfig;
}
type ShouldRenderBlock = (block: Block, blockKey: string) => Boolean;
interface Location {
history?: History;
search?: string;
hash?: string;
pathname?: string;
hostname?: string;
}
interface Locale {
lang?: Lang;
tld?: string;
}
interface SSR {
isServer?: boolean;
}
interface NavigationData {
logo: NavigationLogo;
header: HeaderData;
}
interface NavigationLogo {
icon: ImageProps;
text?: string;
url?: string;
}
interface HeaderData {
leftItems: NavigationItem[];
rightItems?: NavigationItem[];
}
interface NavigationLogo {
icon: ImageProps;
text?: string;
url?: string;
}
The package provides a set of server utilities for transforming your content.
const {fullTransform} = require('@gravity-ui/page-constructor/server');
const {html} = fullTransform(content, {
lang,
extractTitle: true,
allowHTML: true,
path: __dirname,
plugins,
});
Under the hood, a package is used to transform Yandex Flavored Markdown into HTML - diplodoc/transfrom
, so it is also in peer dependencies
You can also use useful utilities in the places you need, for example in your custom components
const {
typografToText,
typografToHTML,
yfmTransformer,
} = require('@gravity-ui/page-constructor/server');
const post = {
title: typografToText(title, lang),
content: typografToHTML(content, lang),
description: yfmTransformer(lang, description, {plugins}),
};
You can find more utilities in this section
The page constructor lets you use blocks that are user-defined in their app. Blocks are regular React components.
To pass custom blocks to the constructor:
Create a block in your app.
In your code, create an object with the block type (string) as a key and an imported block component as a value.
Pass the object you created to the custom.blocks
, custom.headers
or custom.subBlocks
parameter of the PageConstructor
component (custom.headers
specifies the block headers to be rendered separately above general content).
Now you can use the created block in input data (the content
parameter) by specifying its type and data.
To use mixins and constructor style variables when creating custom blocks, add import in your file:
@import '~@gravity-ui/page-constructor/styles/styles.scss';
It's sometimes necessary that a block renders itself based on data to be loaded. In this case, loadable blocks are used.
To add custom loadable
blocks, pass to the PageConstructor
the custom.loadable
property with data source names (string) for the component as a key and an object as a value.
export interface LoadableConfigItem {
fetch: FetchLoadableData; // data loading method
component: React.ComponentType; //blog to pass loaded data
}
type FetchLoadableData<TData = any> = (blockKey: string) => Promise<TData>;
The page constructor uses the bootstrap
grid and its implementation based on React components that you can use in your own project (including separately from the constructor).
Usage example:
import {Grid, Row, Col} from '@gravity-ui/page-constructor';
const Page: React.FC<PageProps> = ({children}) => (
<Grid>
<Row>
<Col sizes={{lg: 4, sm: 6, all: 12}}>{children}</Col>
</Row>
</Grid>
);
Page navigation can also be used separately from the constructor:
import {Navigation} from '@gravity-ui/page-constructor';
const Page: React.FC<PageProps> = ({data, logo}) => <Navigation data={data} logo={logo} />;
Each block is an atomic top-level component. They're stored in the src/units/constructor/blocks
directory.
Sub-blocks are components that can be used in the block children
property. In a config, a list of child components from sub-blocks is specified. Once rendered, these sub-blocks are passed to the block as children
.
page-constructor
In the src/blocks
or src/sub-blocks
directory, create a folder with the block or sub-block code.
Add the block or sub-block name to enum BlockType
orSubBlockType
and describe its properties in the src/models/constructor-items/blocks.ts
or src/models/constructor-items/sub-blocks.ts
file in a similar way to the existing ones.
Add export for the block in the src/blocks/index.ts
file and for the sub-block in the src/sub-blocks/index.ts
file.
Add a new component or block to mapping in src/constructor-items.ts
.
Add a validator for the new block:
schema.ts
file to the block or sub-block directory. In this file, describe a parameter validator for the component in json-schema
format.schema/validators/blocks.ts
or schema/validators/sub-blocks.ts
file.enum
or selectCases
in the schema/index.ts
file.In the block directory, add the README.md
file with a description of input parameters.
In the block directory add storybook demo in __stories__
folder. All demo content for story should be placed in data.json
at story dir. The generic Story
must accept the type of block props, otherwise incorrect block props will be displayed in Storybook.
Add block data template to src/editor/data/templates/
folder, file name should match block type
(optional) Add block preview icon to src/editor/data/previews/
folder, file name should match block type
The PageConstructor
lets you use themes: you can set different values for individual block properties depending on the theme selected in the app.
To add a theme to a block property:
In the models/blocks.ts
file, define the type of the respective block property using the ThemeSupporting<T>
generic, where T
is the type of the property.
In the file with the block's react
component, get the value of the property with the theme via getThemedValue
and useTheme
hook (see examples in the MediaBlock.tsx
block).
Add theme support to the property validator: in the block's schema.ts
file, wrap this property in withTheme
.
The page-constructor
is a uikit-based
library, and we use an instance of i18n
from uikit. To set up internationalization, you just need to use the configure
from uikit:
import {configure} from '@gravity-ui/uikit';
configure({
lang: 'ru',
});
To use maps, put the map type, scriptSrc and apiKey in field mapContext
in PageConstructorProvider
.
You can define environment variables for dev-mode in .env.development file within project root.
STORYBOOK_GMAP_API_KEY
- apiKey for google maps
To start using any analytics, pass a handler to the constructor. The handler must be created on a project side. The handler will receive the default
and custom
event objects. The passed handler will be fired on a button, link, navigation, and control clicks. As one handler is used for all events treatment, pay attention to how to treat different events while creating the handler. There are predefined fields that serve to help you to build complex logic.
Pass autoEvents: true
to constructor to fire automatically configured events.
function sendEvents(events: MyEventType []) {
...
}
<PageConstructorProvider
...
analytics={{sendEvents, autoEvents: true}}
...
/>
An event object has only one required field - name
. It also has predefined fields, which serve to help manage complex logic. For example, counter.include
can help to send event in a particular counter if several analytics systems are used in a project.
type AnalyticsEvent<T = {}> = T & {
name: string;
type?: string;
counters?: AnalyticsCounters;
context?: string;
};
It is possible to configure an event type needed for a project.
type MyEventType = AnalyticsEvent<{
[key: string]?: string; // only a 'string' type is supported
}>;
It is possible to configure an event to which an analytics system to sent.
type AnalyticsCounters = {
include?: string[]; // array of analytics counter ids that will be applied
exclude?: string[]; // array of analytics counter ids that will not be applied
};
Pass context
value to define place in a project where an event is fired.
Use selector below or create logic that serves project needs.
// analyticsHandler.ts
if (isCounterAllowed(counterName, counters)) {
analyticsCounter.reachGoal(counterName, name, parameters);
}
Several predefined event types are used to mark automatically configured events. Use the types to filter default events, for example.
enum PredefinedEventTypes {
Default = 'default-event', // default events which fire on every button click
Play = 'play', // React player event
Stop = 'stop', // React player event
}
npm ci
npm run dev
import react from '@vitejs/plugin-react-swc';
import dynamicImport from 'vite-plugin-dynamic-import';
export default defineConfig({
plugins: [
react(),
dynamicImport({
filter: (id) => id.includes('/node_modules/@gravity-ui/page-constructor'),
}),
],
});
For Vite, you need to install the vite-plugin-dynamic-import
plugin and configure the config so that dynamic imports work
In usual cases we use two types of commits:
Release-As: <version>
to your commit message e.g.git commit -m 'chore: bump release
Release-As: 1.2.3'
You can see all information here.
When you receive the approval of your pull-request from the code owners and pass all the checks, please do the following:
chore(main): release 0.0.0
). If it exists, you should check why it is not merged. If the contributor agrees to release a shared version, follow the next step. If not, ask him to release his version, then follow the next step.If you want to release alpha version of the package from your branch you can do it manually:
alpha
in you manual version otherwise you will get error.If you want to release a new major version, you will probably need for a beta versions before a stable one, please do the following:
beta
.git commit -m 'fix: last commit
Release-As: 3.0.0-beta.0' --allow-empty
beta
with updated CHANGELOG.md and bump version of the packagebeta
to branch main
. Notice that it is normal that your package version will be with beta tag. Robot knows that and change it properly. 3.0.0-beta.0
will become 3.0.0
If you want to release a new version in previous major after commit it to the main, please do the following:
version-1.x.x/fixes
- for major 1.x.xversion-2.x.x
- for major 2.x.xmain
Editor provides user interface for page content management with realtime preview.
How to use:
import {Editor} from '@gravity-ui/page-constructor/editor';
interface MyAppEditorProps {
initialContent: PageContent;
transformContent: ContentTransformer;
onChange: (content: PageContent) => void;
}
export const MyAppEditor = ({initialContent, onChange, transformContent}: MyAppEditorProps) => (
<Editor content={initialContent} onChange={onChange} transformContent={transformContent} />
);
Comprehensive documentation is available at the provided link.
FAQs
Gravity UI Page Constructor
The npm package @gravity-ui/page-constructor receives a total of 718 weekly downloads. As such, @gravity-ui/page-constructor popularity was classified as not popular.
We found that @gravity-ui/page-constructor demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.