vue-modal-dialogs
Advanced tools
Comparing version 2.0.0-alpha.1 to 2.0.0
@@ -9,9 +9,2 @@ 'use strict' | ||
* | ||
* If the name of the first prop is one of the keys of the first argument, | ||
* that argument will be ignored. | ||
* | ||
* e.g. `makeDialog(component, 'title')({ title: 'some title' })`. | ||
* The `title` will be `'some title'`, not the object `{ title: 'some title' }`. | ||
* This will be less ambiguous. | ||
* | ||
* @param {string[]} props | ||
@@ -21,11 +14,8 @@ * @param {any[]} args | ||
function collectProps (props, args) { | ||
if (props.length === 0 && args[0] && typeof args[0] === 'object') { | ||
return args[0] | ||
} | ||
return props.reduce(function (propsData, prop, i) { | ||
if ( | ||
(i !== 0 && args[i] !== undefined) || | ||
typeof args[0] !== 'object' || | ||
args[0][props[0]] === undefined | ||
) { | ||
propsData[prop] = args[i] | ||
} | ||
propsData[prop] = args[i] | ||
return propsData | ||
@@ -44,18 +34,3 @@ }, {}) | ||
}, | ||
transitionName: String, | ||
appear: Boolean, | ||
appearActiveClass: String, | ||
appearClass: String, | ||
appearToClass: String, | ||
css: Boolean, | ||
duration: String, | ||
enterActiveClass: String, | ||
enterClass: String, | ||
enterToClass: String, | ||
leaveActiveClass: String, | ||
leaveClass: String, | ||
leaveToClass: String, | ||
moveClass: String, | ||
tag: String, | ||
type: String | ||
transitionName: String | ||
}, | ||
@@ -65,2 +40,3 @@ data: function () { return ({ | ||
id: 0, | ||
/** All dialogs to render. Dialog render options is stored here */ | ||
@@ -82,5 +58,3 @@ dialogs: {} | ||
// Expose wrapper component | ||
if (!wrappers[this.name]) { | ||
wrappers[this.name] = this | ||
} | ||
wrappers[this.name] = this | ||
}, | ||
@@ -95,3 +69,3 @@ render: function render (createElement) { | ||
props: Object.assign({}, | ||
this.$attrs, this.$props, | ||
this.$options.propsData, | ||
{ name: this.transitionName } | ||
@@ -127,18 +101,8 @@ ), | ||
// Prepare the props of the dialog component | ||
var defaultPropsData = { | ||
var propsData = Object.assign({ | ||
dialogId: id, | ||
arguments: args | ||
} | ||
}, collectProps(options.props, args)) | ||
// If the first argument of the dialog function is an object, | ||
// use it as a part of the propsData | ||
var firstArgObject = typeof args[0] === 'object' ? args[0] : {} | ||
var propsData = Object.assign( | ||
{}, | ||
defaultPropsData, | ||
firstArgObject, | ||
collectProps(options.props, args) | ||
) | ||
return this.pushDialog(Object.assign.apply(Object, [ { id: id, propsData: propsData, promise: promise, resolve: resolve } ].concat( options ))) | ||
return this.pushDialog(Object.assign({ id: id, propsData: propsData, promise: promise, resolve: resolve }, options)) | ||
}, | ||
@@ -145,0 +109,0 @@ |
'use strict' | ||
export var makeDialog = require('./make-dialog').default | ||
export var DialogsWrapper = require('./dialogs-wrapper').default | ||
import Vue from 'vue' | ||
import makeDialog from './make-dialog' | ||
import DialogsWrapper from './dialogs-wrapper' | ||
@@ -12,3 +13,4 @@ var VueModalDialogs = { | ||
makeDialog: makeDialog, | ||
DialogsWrapper: DialogsWrapper | ||
DialogsWrapper: DialogsWrapper, | ||
DialogComponent: Vue | ||
} | ||
@@ -18,1 +20,6 @@ | ||
export default VueModalDialogs | ||
export { | ||
makeDialog, | ||
DialogsWrapper, | ||
Vue as DialogComponent | ||
} |
@@ -54,3 +54,3 @@ 'use strict' | ||
extends: component, | ||
props: diff(['dialogId', 'arguments' ].concat( props), Object.keys(component.props || [])), | ||
props: diff(['dialogId', 'arguments' ].concat( props), Object.keys(component.props || (component.options && component.options.props) || [])), | ||
methods: { | ||
@@ -72,9 +72,10 @@ $close: function $close (data) { | ||
return wrappers[wrapper].add(dialogOptions, args) | ||
} else if (process.env.NODE_ENV !== 'production') { | ||
var message = "[vue-modal-dialogs] Wrapper " + wrapper + " is not found. Make sure that you have added <dialogs-wrapper wrapper-name=\"" + wrapper + "\" /> component somewhere in your project." | ||
return Promise.reject(new Error(message)) | ||
} else { | ||
return Promise.reject() | ||
if (process.env.NODE_ENV !== 'production') { | ||
console.error(("[vue-modal-dialogs] Wrapper " + wrapper + " is not found. Make sure that you have added <dialogs-wrapper wrapper-name=\"" + wrapper + "\" /> component somewhere in your project.")) | ||
} | ||
return Promise.reject(new TypeError(("Undefined reference to wrapper " + wrapper))) | ||
} | ||
} | ||
} |
{ | ||
"name": "vue-modal-dialogs", | ||
"version": "2.0.0-alpha.1", | ||
"version": "2.0.0", | ||
"description": "A Promise-based modal dialog helper for Vue.js", | ||
@@ -77,5 +77,8 @@ "main": "dist/index.js", | ||
"style-loader": "^0.13.1", | ||
"ts-loader": "^3.2.0", | ||
"typescript": "^2.6.2", | ||
"uglify-js": "^3.1.0", | ||
"vue": "^2.5.2", | ||
"vue-loader": "^13.3.0", | ||
"vue-property-decorator": "^6.0.0", | ||
"vue-style-loader": "^3.0.3", | ||
@@ -82,0 +85,0 @@ "vue-template-compiler": "^2.5.2", |
291
README.md
@@ -1,12 +0,21 @@ | ||
[中文文档](https://huajingkun.com/article/vue-modal-dialogs) | [Check demo here!](https://hjkcai.github.io/vue-modal-dialogs) | ||
[Check demo here!](https://hjkcai.github.io/vue-modal-dialogs) | | ||
[1.x docs](https://github.com/hjkcai/vue-modal-dialogs/blob/c0fcd99961f2cc118c2fbadc73efc4e384ab2593/README.md) | ||
# Introduction | ||
Dialogs are a typical and essential user interaction in interactive applications. But implementing dialogs are not an easy thing in front-end web development. | ||
**Promisify dialogs! Every dialog is just a promise!** | ||
`vue-modal-dialogs` is a super light-weighted library aimed to help developers to easily use dialogs by the advantage of [Vue.js](https://vuejs.org), [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), and [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). | ||
Dialogs are a typical and essential user interaction in interactive applications. | ||
But implementing dialogs are not an easy thing in front-end web development. | ||
`vue-modal-dialogs` is a super light-weighted library aimed to | ||
help developers to easily use dialogs by the advantage of [Vue.js](https://vuejs.org), | ||
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), and | ||
[async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). | ||
Feel free to open an issue or PR if you have any questions, problems, or new ideas. | ||
# Features | ||
* ✅ Light weighted (~1kb min+gzip) | ||
* ✅ Light weighted (~2kb min+gzip) | ||
* ✅ Promise based | ||
@@ -21,2 +30,4 @@ * ✅ Functional programming | ||
* ❌ Shortcut function in Vue's prototype | ||
* ❌ Lock the scroll bar | ||
* ❌ Anything but promisify dialogs | ||
@@ -50,3 +61,5 @@ # Installation | ||
// Now you are able to call `message` with 'title' as the first argument and 'content' as the second argument. It returns a Promise so you can easily use it in an async function. | ||
// Now you are able to call `message` with 'title' as the first argument, | ||
// and 'content' as the second argument. | ||
// It returns a Promise so you can easily use it in an async function. | ||
new Vue({ | ||
@@ -64,3 +77,4 @@ template: '<button @click="removeEverything">Click Me!</button>', | ||
In MessageComponent, just call `this.$close` with some data when you are done. It can be done even in component's template. | ||
In MessageComponent, just call `this.$close` with some data when you are done. | ||
It can be done even in component's template. | ||
@@ -96,56 +110,34 @@ ```html | ||
Firstly install `ModalDialogs` as a plugin into Vue. | ||
```javascript | ||
Vue.use(VueModalDialogs, { /* optional options */ }) | ||
Vue.use(ModalDialogs) // No options | ||
``` | ||
You can have a few options here: | ||
Then add a `<dialogs-wrapper>` component into the root component of your project | ||
(typically `App.vue`). See details below. | ||
```typescript | ||
interface PluginOptions { | ||
/** | ||
* Mount point of the wrapper element. All dialogs will be inside this wrapper. | ||
* vue-modal-dialogs automatically creates a new element if that element is not present. | ||
* | ||
* Defaults to `undefined`. | ||
*/ | ||
el?: HTMLElement | string, | ||
## Dialogs wrapper | ||
/** | ||
* Render options of the wrapper element. | ||
* | ||
* This options is the same to VNode's render option. | ||
* You can pass any props/events supported by the <transition-group> component. | ||
* See https://vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth | ||
*/ | ||
wrapper?: WrapperRenderOptions | ||
A dialogs wrapper is nothing but a slightly modified `<transition-group>` component. | ||
/** | ||
* Component options of the dialog wrapper component. | ||
*/ | ||
wrapperComponentOptions?: Vue.ComponentOptions<Vue> | ||
There are two props: | ||
/** | ||
* Options to control the `z-index` css property of each dialog. | ||
* This feature guarantees that the newer dialog is always on the top of the older dialogs. | ||
* You can disable this feature by setting this option to `false`. | ||
*/ | ||
zIndex?: { | ||
/** | ||
* The initial value of `z-index`. | ||
* | ||
* Defaults to `1000`. | ||
*/ | ||
value?: number, | ||
1. `name`: The name of the wrapper. The name must be unique throughout the entire project. | ||
The default value is `default`. | ||
/** | ||
* Indicates if the `z-index` auto increases | ||
* when a new modal dialog is shown. | ||
* | ||
* Defaults to `true`. | ||
*/ | ||
autoIncrement?: boolean | ||
} | ||
} | ||
``` | ||
2. `transition-name`: Alias to the `name` prop of the `<transition-group>` component. | ||
Everything other than these two props, including event listeners, | ||
will be directly passed into the underlying `<transition-group>` component. | ||
You are free to use full-featured `<transition-group>` component. | ||
I strongly recommend that put the wrapper in the root component of your project | ||
(typically `App.vue`). Because, in this case, the wrapper component will never be re-created. | ||
Otherwise there may be wired behaviors of the dialogs. | ||
You can create multiply wrappers at the same time. | ||
Dialogs will go into the wrapper you specifies when calling `makeDialog` function | ||
(see the following section). Usually one wrapper with a default name is enough for most cases. | ||
## Dialog function | ||
@@ -155,3 +147,4 @@ | ||
Call `makeDialog` function to make a dialog function. You can call it by `ModalDialogs.makeDialogs` or import `makeDialog` function like this: | ||
Call `makeDialog` function to make a dialog function. | ||
You can call it by `ModalDialogs.makeDialogs` or import `makeDialog` function like this: | ||
@@ -177,17 +170,21 @@ ```javascript | ||
/** An array that maps the argument list to props */ | ||
props: string[], | ||
props: string[] | ||
/** | ||
* Options to render the dialog component. | ||
* | ||
* This is the same to the VNode's render options. | ||
* See https://vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth | ||
*/ | ||
render?: Vue.VNodeData | ||
/** The wrapper that the dialog will be added into */ | ||
wrapper: string | ||
} | ||
``` | ||
The `props` option is very important. **It maps the arguments of the dialog function to the props of the dialog component**. For example, if `props` is set to `['title', 'content']`, then you call the dialog function like this: | ||
### `component` | ||
The `component` option is the place where to pass your dialog component into. | ||
### `props` | ||
**The `props` option maps the arguments of the dialog function to the props of the dialog component**. | ||
For example, if you call the dialog function like this: | ||
```javascript | ||
// The props option is ['title', 'content'] | ||
const dialogFunction = makeDialog(SomeComponent, 'title', 'content') | ||
dialogFunction('This is title', 'This is content', 'Extra argument') | ||
@@ -203,3 +200,4 @@ ``` | ||
dialogId: 0, // A unique ID of current dialog | ||
// stores all arguments | ||
// Stores all arguments | ||
arguments: ['This is title', 'This is content', 'Extra argument'] | ||
@@ -209,15 +207,67 @@ } | ||
Note that `makeDialog` function is a *pure function* and a *higher-order function*. It does not modify the original component but generate a new components that *extends* the original one. You can use the original component everywhere else as-is. See [https://vuejs.org/v2/api/#extends](https://vuejs.org/v2/api/#extends) for more information. | ||
If you just leave the `props` option **an empty array**, the dialog function can | ||
still pass props into dialog components. In this case it will not map the arguments. | ||
It retrieves the first argument as `propsData`, in another word, a 'data object', | ||
if the first argument is an object. For example: | ||
```javascript | ||
// The props option is [] (an empty array) | ||
const dialogFunction = makeDialog(SomeComponent) | ||
dialogFunction({ | ||
title: 'This is title', | ||
content: 'This is content' | ||
}, 'Extra argument') | ||
``` | ||
The dialog component will receive these props: | ||
```javascript | ||
{ | ||
title: 'This is title', // Data from the first argument | ||
content: 'This is content', | ||
dialogId: 0, // A unique ID of current dialog | ||
// Stores all arguments | ||
arguments: [{ title: 'This is title', content: 'This is content'}, 'Extra argument'] | ||
} | ||
``` | ||
This is useful when you have lots of props to pass into the dialog component. | ||
It is better to use an object instead of a long list of arguments. | ||
If you want to pass props in this way, | ||
**remember to define props in the dialog component**! | ||
The first argument will be treated as the data object | ||
**only when `props` option is empty**! If you want to mix those two usages, | ||
do it yourself. | ||
### `wrapper` | ||
The `wrapper` option specifies which dialogs wrapper to put the dialog component into. | ||
The default value is `default` (it is the same to the default value of the `name` prop | ||
of the dialogs wrapper component). In most cases you do not need to set this option. | ||
The promise from the dialog function will reject only when the specified wrapper is not found. | ||
Note that `makeDialog` function is a *pure function* and a *higher-order function*. | ||
It does not modify the original component but generate a new components that | ||
*extends* the original one. You can use the original component everywhere else as-is. | ||
See [https://vuejs.org/v2/api/#extends](https://vuejs.org/v2/api/#extends) for more information. | ||
## Dialog component | ||
Dialog components will be shown later when you call the dialog function. Here you need to decide how your dialog looks like. | ||
Dialog components will be shown later when you call the dialog function. | ||
Here you need to decide how your dialog looks like. | ||
Typically there are some arguments passed into the dialog function. They can be easily retrieved from props. These props will be defined *automatically*. If you want to [validate these props](https://vuejs.org/v2/guide/components#Prop-Validation), just define them by yourself with some validation. | ||
Typically there are some arguments passed into the dialog function. | ||
They can be easily retrieved from props. These props will be defined *automatically*. | ||
If you want to [validate these props](https://vuejs.org/v2/guide/components#Prop-Validation), | ||
just define them by yourself with some validation. | ||
However, if you would like to use data objects to pass props, | ||
**you need to manually define all props** in the component. | ||
Otherwise the data will be *never* received in the component. | ||
```javascript | ||
export default { | ||
// Here are two dialog arguments. You can use these props as normal props. | ||
// In fact, vue-modal-dialogs has already defined these props for you. | ||
// You may not define these props unless you want to do some validation. | ||
props: { | ||
@@ -231,21 +281,106 @@ title: String, | ||
Additionally, two props will always be available in every dialog component: | ||
Additionally, one method and two props will always be available in every dialog component. | ||
You can always omit the declaration of them: | ||
1. `arguments`: This is an array containing everything you passed in the dialog function. | ||
2. `dialogId`: A unique ID of current dialog. This is an internal data of `vue-modal-dialogs`. | ||
1. `$close`: A 'callback'. Call this method when you are done (e.g. user pressing the OK button), | ||
with the data you want to send back. It will close the current dialog component, | ||
and resolve the previous created Promise. | ||
A `$close` method will be added into this component automatically. Call this method with data when you are done (e.g. user pressing the OK button). It will close the current dialog component and resolve the previous created Promise. | ||
```javascript | ||
this.$close(data) // data is optional | ||
``` | ||
```javascript | ||
this.$close(data) // data is optional | ||
2. `arguments`: This is an array containing everything you passed in the dialog function. | ||
See examples in the previous section. | ||
3. `dialogId`: A unique ID of current dialog. This is an internal data of `vue-modal-dialogs`. | ||
This might be useful when you need an auto-increment index. | ||
The same component can be used many many times for creating dialog functions, | ||
since the component will *never* be modified. | ||
## TypeScript | ||
vue-modal-dialogs have *partial* support for TypeScript. | ||
If you want to use TypeScript, you must use Vue 2.5 or above. | ||
And [vue-property-decorator](https://github.com/kaorun343/vue-property-decorator) | ||
are recommended. | ||
Dialog components can be defined by extending the base class `DialogComponent<ReturnType>`: | ||
```typescript | ||
import { DialogComponent } from 'vue-modal-dialogs' | ||
import { Prop, Component } from 'vue-property-decorator' | ||
@Component({}) | ||
export default class SomeComponent extends DialogComponent<boolean> { | ||
@Prop() title: string | ||
@Prop() content: string | ||
ok () { | ||
this.$close(true) | ||
} | ||
render (h) { | ||
return ( | ||
<div onClick={ this.ok }> | ||
<div>{ this.title }</div> | ||
<div>{ this.content }</div> | ||
</div> | ||
) | ||
} | ||
} | ||
``` | ||
You can make several dialog components and then use them for making dialog functions. | ||
Argument types of the dialog function is unknown. | ||
Manually specify via the `makeDialog` generic: | ||
```typescript | ||
// returns (prop1: string, prop2: string) => Promise<boolean> | ||
makeDialog<string, string>(SomeComponent, 'title', 'content') | ||
``` | ||
If you want to use 'data object' as the first argument, you need to define | ||
what to pass into the dialog function by using object type definitions or interfaces. | ||
```typescript | ||
interface ConfirmData { | ||
title: string, | ||
content: string | ||
} | ||
// returns (data: ConfirmData) => Promise<boolean> | ||
makeDialog<ConfirmData>(SomeComponent) | ||
// returns (data: { title: string, content: string }) => Promise<boolean> | ||
makeDialog<{ title: string, content: string }>(SomeComponent) | ||
``` | ||
Unfortunately, if `SomeComponent` is defined in a `.vue` file, the type of | ||
`SomeComponent` will be `VueConstructor` instead of `DialogComponent<boolean>`. | ||
This results in unknown return type of the dialog function (it becomes to `any`). | ||
You have to annotate it yourself: | ||
```typescript | ||
import SomeComponent from './comp.vue' | ||
// returns (prop1: string, prop2: string) => Promise<any> | ||
makeDialog<string, string>(SomeComponent, 'title', 'content') | ||
// returns (prop1: string, prop2: string) => Promise<boolean> | ||
makeDialog<string, string, boolean>(SomeComponent, 'title', 'content') | ||
// returns (data: ConfirmData) => Promise<any> | ||
makeDialog<ConfirmData>(SomeComponent) | ||
// returns (data: ConfirmData) => Promise<boolean> | ||
makeDialog<ConfirmData, boolean>(SomeComponent) | ||
``` | ||
# Contribution | ||
Issues and PRs are welcomed! | ||
## Run development server | ||
Run example in development mode: | ||
```bash | ||
@@ -261,2 +396,4 @@ # use npm | ||
The built file will be generated in the `dist` folder. | ||
```bash | ||
@@ -263,0 +400,0 @@ # use npm |
@@ -9,3 +9,3 @@ import Vue, { ComponentOptions } from 'vue' | ||
export interface DialogComponent<ReturnType> extends Vue { | ||
export declare class DialogComponent<ReturnType> extends Vue { | ||
/** The unique id of this dialog */ | ||
@@ -21,4 +21,10 @@ readonly dialogId: number | ||
export type Component<ReturnType, PropsDef> = ComponentOptions<Vue, DialogComponent<ReturnType> & PropsDef> | VueConstructor | ||
interface DialogComponentConstructor<ReturnType> { | ||
new (): DialogComponent<ReturnType> | ||
} | ||
export type Component<ReturnType, PropsDef> = DialogComponentConstructor<ReturnType> | | ||
ComponentOptions<DialogComponent<ReturnType> & PropsDef> | | ||
VueConstructor | ||
/** Options to build a dialog function */ | ||
@@ -37,9 +43,9 @@ export interface DialogOptions<ReturnType, PropsDef> { | ||
interface DialogFunction<ReturnType = any, PropsDef extends object = {}> { | ||
(data?: Partial<PropsDef>): DialogPromise<ReturnType> | ||
(...args: any[]): DialogPromise<ReturnType> | ||
(data?: PropsDef): DialogPromise<ReturnType> | ||
} | ||
export declare function makeDialog< | ||
ReturnType = any, | ||
PropsDef extends object = {} | ||
PropsDef extends object = {}, | ||
ReturnType = any | ||
> ( | ||
@@ -50,11 +56,11 @@ options: DialogOptions<ReturnType, PropsDef> | ||
export declare function makeDialog< | ||
ReturnType = any, | ||
PropsDef extends object = {} | ||
PropsDef extends object = {}, | ||
ReturnType = any | ||
> ( | ||
component: Component<ReturnType, PropsDef> | ||
): (data?: PropsDef) => DialogPromise<ReturnType> | ||
): (data?: Partial<PropsDef>) => DialogPromise<ReturnType> | ||
export declare function makeDialog< | ||
Arg1 = any, | ||
ReturnType = any, | ||
Arg1 = any, | ||
PropsDef extends object = {} | ||
@@ -67,5 +73,5 @@ > ( | ||
export declare function makeDialog< | ||
ReturnType = any, | ||
Arg1 = any, | ||
Arg2 = any, | ||
ReturnType = any, | ||
PropsDef extends object = {} | ||
@@ -79,6 +85,6 @@ > ( | ||
export declare function makeDialog< | ||
ReturnType = any, | ||
Arg1 = any, | ||
Arg2 = any, | ||
Arg3 = any, | ||
ReturnType = any, | ||
PropsDef extends object = {} | ||
@@ -93,2 +99,6 @@ > ( | ||
export declare function makeDialog< | ||
Arg1 = any, | ||
Arg2 = any, | ||
Arg3 = any, | ||
Arg4 = any, | ||
ReturnType = any, | ||
@@ -98,2 +108,30 @@ PropsDef extends object = {} | ||
component: Component<ReturnType, PropsDef>, | ||
prop1: string, | ||
prop2: string, | ||
prop3: string, | ||
prop4: string | ||
): (arg1: Arg1, arg2: Arg2, arg3: Arg3, arg4: Arg4) => DialogPromise<ReturnType> | ||
export declare function makeDialog< | ||
Arg1 = any, | ||
Arg2 = any, | ||
Arg3 = any, | ||
Arg4 = any, | ||
Arg5 = any, | ||
ReturnType = any, | ||
PropsDef extends object = {} | ||
> ( | ||
component: Component<ReturnType, PropsDef>, | ||
prop1: string, | ||
prop2: string, | ||
prop3: string, | ||
prop4: string, | ||
prop5: string | ||
): (arg1: Arg1, arg2: Arg2, arg3: Arg3, arg4: Arg4, arg5: Arg5) => DialogPromise<ReturnType> | ||
export declare function makeDialog< | ||
PropsDef extends object = {}, | ||
ReturnType = any | ||
> ( | ||
component: Component<ReturnType, PropsDef>, | ||
...props: string[] | ||
@@ -100,0 +138,0 @@ ): DialogFunction<ReturnType, PropsDef> |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
25930
323
0
396
0
51