1.2.823 • Published 3 years ago

goodteditor-web-components v1.2.823

Weekly downloads
19,817
License
ISC
Repository
-
Last release
3 years ago

Общие сведения

Данный проект содержит набор компонентов (далее widget) для СУПа.

  • component инкапсулированный vue компонент, выполняющий какую-либо бизнес логику.
  • panel vue компонент(ы), который настраивает по средством ui свойства component, которые описаны в дескрипторе.
  • widget совокупность component + panel

DEV mode

Для упрощения разработки, в состав пакета входит приложение, эмулирующее среду редактора СУП. Запуск dev режима npm run serve

Для отправки событий через шину событий, используйте window.eventBus

Зависимости

Соглашения

  1. в проекте используется спецификация es6 (так же nullish-coalescing, spread, object rest spread, optional-chaining)

  2. названия component должны начинаться с префикса Elem... используя CamelCase (например ElemExample)

  3. названия panel должны иметь суффикс ...Panel используя CamelCase (например SettingsPanel) и распологаться в директории ./panels (относительно директории виджета)

  4. widget должны находится в директории src/lib/, каждый отдельный компонент должен находится в своей директории, имя которой должно совпадать с именем компонента. Например src/lib/ElemExample/ElemExample.vue

  5. widget могут использовать свое пространство имен namespace, заданный директориями любой вложенности (например src/lib/<namespace>/ElemExample/ElemExample.vue)

  6. widget могут не использовать пространство имен только в случае, если данные виджет являются базовыми компонентами, не привязанными к какому-либо проекту (например 'кнопка')

  7. src/utils/ директория, которая содержит общие зависимости, доступные всем widget (утилиты, классы, сервисы и тд). Зависимости в данной директории должны быть общего назначения, не привязанными к какому-либо проекту (например утилиты для работы с http, socket, датами и тд)

  8. Группировать widget в рамках одного проекта следует как минимум одним уровнем вложенности namespace (например src/lib/<projectname>/<namespace>/ElemExample/...)

  9. Утилиты, специфичные для конкретного проекта или widget должны храниться в соответствующей директории. Пример: src/lib/MyProject/utils/... утилиты MyProject src/lib/Myproject/ElemExample/utils/... утилиты widget MyProject/ElemExample

  10. Для форматирования кода используется prettier, который уже настроен в проекте. Вы можете установить плагин prettier для вашей IDE https://prettier.io/docs/en/editors.html

  11. В проекте необходимо стремиться к мин. кол-ву внешних зависимостей, добавление новых в проект обсуждается заранее.

  12. У виджетов обязательно должен быть dom-node (должен рендериться в любом случае).

Резюме:

EventBus diagram EventBus diagram

Widget component

Каждый component наследуется от базового компонента Elem src/lib/Elem.vue Публичный интерфейс базового Elem.vue

template

Базовый шаблон component

<template>
    <div :class="cssClass" :style="cssStyle"></div>
</template>

descriptor

descriptor объект, который описывает свойства и переменные component. Используется как окружением, так и при написании панелей.

let descriptor = () => ({
    // описание свойств
    // повторяет интерфейс @see https://vuejs.org/v2/api/#props
    props: {
        "<название-свойства>": {
            // @required
            // json-compatible тип свойства: String, Number, Boolean, Array, Object.
            // Так же можно указывать массивом несколько типов например [String,Array]
            type: "<тип-свойства>"
            // @required
            // default значение свойства.
            // фабричный метод, если значение Array, Object
            default: "<значение>"
            // @optional
            // массив вариантов значений свойства (для удобства написания панелей)
            options: [
                { value:"<значение>", label:"<название>" }
            ]
        }
    }
    // описание переменных
    // данное описание используется окружением для настройки props.varAliases
    // используются для настройки работы с шиной событий при использовании методов: `eventBusWrapper.listenStateChange()`, `eventBusWrapper.triggerStateChange()`
    vars: {
        "<название-переменной>": {
            // @required
            // описание
            description: "<описание>",
            // @optional
            // int, используется редактором в ui для сортировки списка переменных (asc)
            sortIndex: "<индекс-сортировки>"
        }
    }
});

props

props: {
    // уникальный идентификатор инстанса компонента, задается окружением
    id: {
        default: ''
    }
    // тип - полное имя компонента в рамках соглашений, задается окружением (например `Project/MyElem`)
    type: {
        type: String,
        default: ''
    }
    // объект, содержащий свойства самого компонента, задается окружением
    props: {
        type: Object,
        default() {
            return getDescriptorDefaultProps(descriptor());
        }
    }
    // режим работы окружения: true - компонент создан в плеере; false - компонент создан в редакторе; задается окружением
    isEditorMode: {
        type: Boolean,
        default: false
    }
}

data

data() {
    return {
        // css class рутового элемента шаблона компонента
        cssClass: {}
        // css style рутового элемента шаблона компонента
        cssStyle: {}
        // дескриптор с описанием свойств компонента
        descriptor: descriptor()
        // имя текущего активного <slot> элемента, который будет использоваться при drag'n'drop в редакторе
        slotDefault: 'default'
        // шина событий @see https://www.npmjs.com/package/goodteditor-event-bus
        eventBusWrapper: null
    }
}

methods

methods: {
    /**
     * Хелпер для вызова super методов при наследовании.
     * Важно! Не забывайте менять контекст вызова методов родителя на свой
     * @example this.super(Elem).genCssClass.call(this)
     * @param {VueComponent} ParentComponent    parent компонент, почти всегда будет Elem
     * @return {object}                         объект с методами ParentComponent
     */
    super(ParentComponent);
    /**
     * Генерирует css class в @see this.cssClass
     * @invoked created()
     */
    genCssClass();
    /**
     * Генерирует css style свойства в @see this.cssStyle
     * @invoked created()
     */
    genCssStyle();
    /**
     * Возвращает список имен слотов, доступных в шаблоне элемента
     * @NOTE <slot></slot> без имени имеют имя 'default'
     * @return {array<String>}  @default ['default']
     */
    getSlotNames();
    /**
     * Возвращает массив async import() промисов модулей компонентов-панелей
     * @return {array<Promise>}   модули компонентов-панелей @example [ import('...') ] @default []
     */
    getPanels();
    /**
     * Возвращает true если разрешено создавать дочерние компоненты указанного типа
     * @param {string} type     тип дочернего элемента @see props.type
     * @return {boolean}        true разрешено; иначе false @default true
     */
    isChildAllowed(type);
    /**
     * Вызывается окружением. Этап life-cycle.
     * Метод гарантирует доступность @see data.eventBusWrapper
     * Вызывается после mounted()
     */
    subscribe();
    /**
     * Вызывается окружением. Сеттер для шины событий.
     * Получается ссылку на шину событий, создает инстанс EventBusWrapper и вызывает метод @invoke subscribe()
     * @param {object} eventBus
     */
    setEventBus(eventBus);
    /**
     * Получение значения константы из инстанса менеджера констант ConstManager
     * @param {string} key
     * @return {*}
     */
    $c(key);
}

Widget panel

Каждая panel наследуется от базового компонента Panel src/lib/Panel.vue Публичный интерфейс базового Panel.vue

props

props: {
    /** @type {VueComponent} ссылка на инстанс компонента виджета */
    elementInstance: {
        type: Object
    },
},

data

data() {
    return {
        // мета данные панели для окружения редактора ( name:String ~ название; icon:String ~ mdi icon class)
        $meta: { name: '', icon: '' },
        // объект, который хранит тек. настройки виджета (мутабелен)
        props: {},
        // дескриптор компонента виджета (описывает атрибуты объекта props, переменные виджета)
        descriptor
    };
},

methods

methods: {
    /**
     * Триггерит 'change' евент, оповещая окружение редактора об изменеии 'props' объекта
     * @param {string} propName     название атрибута, который изменился в 'props' объекте или null для перезаписи всего объекта @default null
     */
    propChanged((propName = null));
}

UI

Для описания ui панелей в проекте есть специальный пакет ui компонентов. При наследовании от Panel.vue ui компоненты автоматически импортируются и доступны с префиксом ui-. Для запуска документации по ui компонентам используйте команду

npm run docs:panel-ui

Пример использования ui в панели

// MyPanel.vue
<template>
    <div>
        <ui-input>label</ui-input>
        <ui-input-browse>label</ui-input-browse>
        <ui-switch>label</ui-switch>
        ...
    </div>
</template>
<script>
import Panel from '<src-path>/lib/Panel';

export default {
    extends: Panel,
    ...
};
</script>

Шина событий

Для взаимодействия между компонентами component и с окружением (плеер, редактор), используется событийная модель, реализованная через шину событий EventBus.

EventBus diagram

Для чего component нужна шина событий:

  • для навигации в плеере eventBusWrapper.listenNavigate(), eventBusWrapper.triggerNavigate()
  • для получения состояния в плеере (глобальный state) eventBusWrapper.listenStateChange(), eventBusWrapper.triggerStateChange()
  • для отправки кастомных событий eventBusWrapper.listen(), eventBusWrapper.trigger()

В каждом инстансе component создается инстанс обертки для работы с шиной событий EventBusWrapper, доступный внутри компонента как this.eventBusWrapper.

Для получения доступа к шине, необходимо в компоненте-наследнике переопределить метод life-cycle subscribe()

// ElemTest.vue
...
export default {
    extends: Elem,
    ...
    methods: {
        /**
         * Переопределяем метод Elem.subscribe()
         */
        subscribe() {
            // теперь шина событий доступна
            this.eventBusWrapper;
        }
    }
}

интерфейс EventBusWrapper

{
    /**
     * Подписывается обработчик на событие "навигации" по страницам в окружении (плеере)
     * @param {function} handler    обработчик вида (e, { url:String, params:Object }) => {}
     * @param {boolean} once        true для разовой подписки @default false
     * @return {function}           dispose метод, для отписки
     */
    listenNavigate(handler, once = false);
    /**
     * Отправка события "навигация" по страницам в окружении.
     * Вызовет переход по роуту в окружении/переход по внешнему url.
     * Если url относительный вида '/...' окружение попытается найти роут и перейти по нему
     * Если url абсолютный вида '<protocol>://' будет вызван window.location
     * @param {object} data     объект вида { url:String, params:Object }
     *                          params ~ query параметры запроса
     * @return {boolean}        true если был осуществлен переход по роуту
     */
    triggerNavigate({ url, params = {} });
    /**
     * Подписывается обработчик на событие "изменение состояния" в окружении (плеере)
     * @param {function} handler    обработчик вида (e, state:Object) => {}
     * @param {boolean} once        true для разовой подписки @default false
     * @return {function}           decoratedHandler декорированный обработчик, для отписки @see unlistenStateChange()
     */
    listenStateChange(handler, once = false);
    /**
     * Отправка события "изменение состояния" в окружение
     * @param {object} stateChange      объект "изменение-состояния"
     */
    triggerStateChange(stateChange);
    /**
     * Подписывает обработчик на событие eventType
     * @param {string} eventType    тип события
     * @param {function} handler    обработчик вида (e, data) => {}
     * @param {boolean} once        true для разовой подписки @default false
     * @return {function}           dispose метод, для отписки
     */
    listen(eventType, handler, once = false);
    /**
     * Отправка события eventType
     * @param {string} eventType    тип события
     * @param {object} data         объект для отправки вместе с событием
     */
    trigger(eventType, data);
}

Так же в интерфейсе есть методы для явной отписки от событий. Их можно не использовать т.к. базовый компонент-родитель Elem сам производит отписку от всех евентов в хуке life-cycle beforeDestroy()

{
    /**
     * Отписывает от события "навигация" обработчик
     * @param {function} handler    обработчик, ранее подписанный @see listenNavigate()
     */
    unlistenNavigate(handler);
    /**
     * Отписывает от события "изменение состояния" обработчик
     * @param {function} decoratedHandler    декорированный обработчик, ранее подписанный @see listenStateChange()
     */
    unlistenStateChange(decoratedHandler);
    /**
     * Отписывает от события eventType обработчик
     * @param {string} eventType    тип события
     * @param {function} handler    обработчик
     */
    unlisten(eventType, handler);
}

Кейсы

Частовозникающие задачи/проблемы при написании widget

Кейс: css/style

Кейс: widget компонент не реагирует на изменение props стандартной панели StylePanel

Для корректной работы props, унаследованных от Elem.vue, необходимо объявить cssClass, cssStyle в шаблоне своего widget компонента/

Пример: ElemExample.vue

<template>
    <div :class="cssClass" :style="cssStyle"></div>
</template>

Кейс: css переменные фреймворка

Кейс: хочу использовать свои css scoped классы в шаблоне widget компонента, но при этом использовать css переменные фреймворка

Css фреймворк имеет массу компонентов, утилит. Однако он не способен удовлетворить все потребности. Если вам необходимо описать свой scoped класс, вы можете использовать css переменные, используя var(). Список переменных: https://goodt-css.netlify.app/#about/about-cssvars

Пример: ElemExample.vue

<template>
    <div :class="cssClass" :style="cssStyle">...</div>
</template>

<style scoped>
    .my-class {
        background: var(--color-primary);
    }
</style>

Кейс: state

Кейс: хотим работать с глобальным state шины событий (читать, писать) в widget компоненте.

Для работы с глобальным state окружения, доступного всем component используются методы: EventBusWrapper.listenStateChange() и EventBusWrapper.triggerStateChange().

Но так же следует зарегистрировать переменные в vars дескрипторе, которые component собирается использовать при работе с state.

Пример: ElemExample.vue

let descriptor = () => ({
    props: {},
    vars: {
        // объявляем явно переменную, которую хотим получать/отправлять из/в state
        myVariable: {
            description: 'date description'
        }
    }
});
export default {
    ...
    subscribe() {
        // подписываемся на изменение 'state' окружения
        this.eventBusWrapper.listenStateChange((e, state) => {
            // ловим свою переменную
            console.log(e, state.myVariable)
        });
        // отправляем свою переменную в 'state' окружения
        this.eventBusWrapper.triggerStateChange({ myVariable: 'Hello world!' })
    }
};

Кейс: навигация

Кейс: хотим вызывать навигацию по страницам из widget компонента.

Для навигации в окружении, доступного всем component используются методы: EventBusWrapper.listenNavigate() и EventBusWrapper.triggerNavigate().

Пример: ElemExample.vue

export default {
    ...
    subscribe() {
        // подписываемся на 'navigate' окружения
        this.eventBusWrapper.listenNavigate((e, { url, params }) => {
            // ловим
            console.log(e, url, params)
        });
        // триггерим навигацию на новый route в окружении -> '/profile'
        this.eventBusWrapper.triggerNavigate({ url:'/profile' })
    }
};

Кейс: panel group

Кейс: при написании панели виджета, есть необходимость разбить настройки панели на группы

Пример: ElemExamplePanel.vue

<template>
    <div>
        <panel-group :panels="panels">
            <template #first-panel>
                <div>First panel content</div>
            </template>
            <template #second-panel>
                <div>Second panel content</div>
            </template>
        </panel-group>
    </div>
</template>
<script>
import Panel from '<src-path>/lib/Panel';
import ElemExample from '<src-path>/lib/ElemExample';
import PanelGroup from '<src-path>/utils/components/PanelGroup';

let descriptor = ElemExample.data().descriptor;

export default {
    extends: Panel,
    components: {
        PanelGroup
    },
    data() {
        return {
            descriptor,
            panels: [
                { slot: 'first-panel', label: 'First panel', visible: true },
                { slot: 'second-panel', label: 'Second panel' }
            ]
        };
    }
};
</script>

Кейс: popup

Кейс: хотим использовать popup внутри своего widget компонента.

Для работы с popup в окружении, необходимо использовать компонент Portal из portal-vue.

Пример: ElemExample.vue

<template>
    <div :class="cssClass" :style="cssStyle">
        <popup :visible.sync="isPopupActive">
            <template #body>
                <h3>Popup body</h3>
                <div>Content goes here...</div>
            </template>
            <template #footer="{ close }">
                <div class="text-right">
                    <div class="btn btn-primary" @click="close">close</div>
                </div>
            </template>
        </popup>
    </div>
</template>

<script>
import Elem, { getDescriptorDefaultProps } from '<src-path>/lib/Elem';
import Popup from '<src-path>/utils/components/Popup';

let descriptor = () => ({
    props: {},
    vars: {}
});

export default {
    extends: Elem,
    components: {
        Popup
    },
    props: {
        props: {
            default() {
                return getDescriptorDefaultProps(descriptor());
            }
        }
    },
    data() {
        return {
            // дескриптор
            descriptor: descriptor(),
            isPopupActive: true
        }
    },
};
</script>

Кейс: работа с константами окружения

Кейс: хотим использовать константы окружения в качестве значений props своего widget компонента.

Пример: ElemExample.vue Для получения значения константы окружения в компоненте widget нужно использовать метод $c()

<template>
    <div :class="cssClass" :style="cssStyle">
        {{ $c(props.message) }}
    </div>
</template>

<script>
import Elem, { getDescriptorDefaultProps } from '<src-path>/lib/Elem';

let descriptor = () => ({
    props: {
        message: {
            type: String,
            default: ''
        }
    },
    vars: {}
});

export default {
    extends: Elem,
    props: {
        props: {
            default() {
                return getDescriptorDefaultProps(descriptor());
            }
        }
    },
    data() {
        return {
            // дескриптор
            descriptor: descriptor()
        };
    },
    created() {
        // для теста
        this.$watch('props.message', (val) => console.log('props.message', this.$c(val)))
    }
};
</script>

Пример: ElemExamplePanel.vue Для получения списка имен констант окружения в панели widget, следует использовать computed свойство envConstantsNames

<template>
    <div>
        <div class="form-label">message</div>
        <div class="form-control w-100">
            <input-autocomplete
                class="w-100"
                size="small"
                v-model="props.message"
                @change="propChanged()"
                :options="envConstantsNames"
            ></input-autocomplete>
        </div>
    </div>
</template>
<script>
import Panel from '<src-path>/lib/Panel';
import ElemExample from '<src-path>/lib/ElemExample';
import { InputAutocomplete } from 'goodteditor-ui';

let descriptor = ElemExample.data().descriptor;

export default {
    extends: Panel,
    components: { InputAutocomplete },
    data() {
        return {
            descriptor
        };
    }
};
</script>

Кейс: dremio

Кейс: хотим использовать dremio в качестве источника данных внутри своего widget компонента.

Для работы с dremio в окружении, необходимо подключить node-module goodt-dremio-sdk, а так же панель lib/DremioPanel.

Пример: ElemExample.vue

<template>
    <div :class="cssClass" :style="cssStyle">
        <template v-if="queryHelper">{{ result }}</template>
        <template v-else>Dremio не настроен</template>
    </div>
</template>

<script>
import Elem, { getDescriptorDefaultProps } from '<src-path>/lib/Elem';
import { Query, SDKFactory } from '<src-path>/utils/dremio/SDK';
import cloneDeep from 'lodash/cloneDeep';

let descriptor = () => ({
    props: {
        dremio: {
            type: Object,
            default: null
        }
    },
    vars: {}
});

export default {
    extends: Elem,
    props: {
        props: {
            default() {
                return getDescriptorDefaultProps(descriptor());
            }
        }
    },
    data() {
        return {
            // дескриптор
            descriptor: descriptor(),
            // тут будут наши данные из сервиса
            result: null,
            // ошибка запроса
            error: null,
            // query помощник
            queryHelper: null,
            // sdk
            dremioSdk: SDKFactory()
        };
    },
    created() {
        let handler = () => {
            // источника пока нет
            if (!this.props.dremio) {
                this.result = null;
                return;
            }
            // отдаем источник на управление
            this.queryHelper = new Query(cloneDeep(this.props.dremio));
            this.loadData();
        };
        if (this.isEditorMode) {
            this.$watch('props.dremio', handler, { deep: true });
        }
        handler();
    },
    methods: {
        // возвращаем промис с компонентом нашей панели
        getPanels() {
            return [import('<src-path>/lib/DremioPanel')];
        },
        // метод для загруки данных
        loadData() {
            let query = this.queryHelper.buildQuery();
            dremioSdk.getData(query)
                .then(result => (this.result = result))
                .catch(e => (this.error = e));
        }
    }
};
</script>

Быстрый старт

Scaffolding

Для упрощения написания виджетов можно воспользоваться cli утилитой создания виджетов, вызвав команду:

npm run scaffold

Manual

Создаем файловую структуру:

src/lib/test/ ~
-- ElemTest/ ~
---- ElemTest.vue

Файл ElemTest.vue:

<template>
    <div :class="cssClass" :style="cssStyle">
        {{props.result}}
    </div>
</template>

<script>
import Elem, { getDescriptorDefaultProps } from './../../Elem';

let descriptor = () => ({
    // props описывает свойства, которые задаются из СУП и настраиваются панелью
    props: {
        message: {
            type: String,
            default: 'Default message'
        }
    },
    vars: {}
});

export default {
    extends: Elem,
    props: {
        props: {
            default() {
                return getDescriptorDefaultProps(descriptor());
            }
        }
    },
    data() {
        return {
            // дескриптор
            descriptor: descriptor()
        }
    },
    methods: {
        // наш компонент не предполагает наличия других дочерних компонентов
        isChildAllowed(type) {
            return false;
        },
        // наш компонент не имеет <slot>
        getSlotNames() {
            return [];
        },
        // возвращаем промис с компонентом нашей панели
        getPanels() {
            return [ import('./ElemTestPanel') ];
        }
    }
}
</script>

Файл ElemTestPanel.vue:

<template>
    <div>
        <div class="p">
            <div class="form-label">message</div>
            <div class="form-control w-100">
                <textarea class="textarea w-100" v-model="props.message" @change="propChanged"></textarea>
            </div>
        </div>
    </div>
</template>
<script>
import Panel from './../../Panel';
import Component from './ElemTest';

// если нам обходим дескриптор с описанием параметров
let descriptor = Component.data().descriptor;

export default {
    extends: Panel,
    data() {
        return {
            descriptor
        }
    }
}
</script>

Всё готово для теста. Открываем файл src/App.vue меняем код:

{
    // полный путь до нашего компонента, относительно `src/lib/` ~ `<namespace>/<elem-comp>`
    elemTypes: ['test/ElemTest'];
}

Запускаем dev режим npm run serve

Результат:

screenshot

1.2.820

3 years ago

1.2.822

3 years ago

1.2.821

3 years ago

1.2.817

3 years ago

1.2.816

3 years ago

1.2.819

3 years ago

1.2.818

3 years ago

1.2.823

3 years ago

1.2.815

3 years ago

1.2.814

3 years ago

1.2.813

3 years ago

1.2.812

3 years ago

1.2.811

3 years ago

1.2.810

3 years ago

1.2.809

3 years ago

1.2.808

3 years ago

1.2.807

3 years ago

1.2.806

3 years ago

1.2.805

3 years ago

1.2.802

3 years ago

1.2.804

3 years ago

1.2.803

3 years ago

1.2.801

3 years ago

1.2.800

3 years ago

1.2.798

3 years ago

1.2.799

3 years ago

1.2.797

3 years ago

1.2.794

3 years ago

1.2.793

3 years ago

1.2.796

3 years ago

1.2.789

3 years ago

1.2.788

3 years ago

1.2.790

3 years ago

1.2.792

3 years ago

1.2.791

3 years ago

1.2.787

3 years ago

1.2.786

3 years ago

1.2.783

3 years ago

1.2.785

3 years ago

1.2.784

3 years ago

1.2.778

3 years ago

1.2.777

3 years ago

1.2.782

3 years ago

1.2.781

3 years ago

1.2.780

3 years ago

1.2.779

3 years ago

1.2.776

3 years ago

1.2.774

3 years ago

1.2.775

3 years ago

1.2.772

3 years ago

1.2.773

3 years ago

1.2.771

3 years ago

1.2.770

3 years ago

1.2.769

3 years ago

1.2.768

3 years ago

1.2.767

3 years ago

1.2.766

3 years ago

1.2.763

3 years ago

1.2.765

3 years ago

1.2.764

3 years ago

1.2.761

3 years ago

1.2.762

3 years ago

1.2.760

3 years ago

1.2.758

3 years ago

1.2.759

3 years ago

1.2.757

3 years ago

1.2.756

3 years ago

1.2.755

3 years ago

1.2.754

3 years ago

1.2.753

3 years ago

1.2.750

3 years ago

1.2.752

3 years ago

1.2.751

3 years ago

1.2.749

3 years ago

1.2.748

3 years ago

1.2.745

3 years ago

1.2.747

3 years ago

1.2.746

3 years ago

1.2.744

3 years ago

1.2.743

3 years ago

1.2.742

3 years ago

1.2.741

3 years ago

1.2.740

3 years ago

1.2.739

3 years ago

1.2.738

3 years ago

1.2.737

3 years ago

1.2.734

3 years ago

1.2.736

3 years ago

1.2.735

3 years ago

1.2.732

3 years ago

1.2.731

3 years ago

1.2.733

3 years ago

1.2.730

3 years ago

1.2.727

3 years ago

1.2.729

3 years ago

1.2.728

3 years ago

1.2.726

3 years ago

1.2.725

3 years ago

1.2.724

3 years ago

1.2.723

3 years ago

1.2.722

3 years ago

1.2.721

3 years ago

1.2.720

3 years ago

1.2.718

3 years ago

1.2.717

3 years ago

1.2.719

3 years ago

1.2.716

3 years ago

1.2.715

3 years ago

1.2.714

3 years ago

1.2.713

3 years ago

1.2.712

3 years ago

1.2.711

3 years ago

1.2.710

3 years ago

1.2.709

3 years ago

1.2.708

3 years ago

1.2.707

3 years ago

1.2.706

3 years ago

1.2.705

3 years ago

1.2.704

3 years ago

1.2.703

3 years ago

1.2.702

3 years ago

1.2.701

3 years ago

1.2.700

3 years ago

1.2.697

3 years ago

1.2.699

3 years ago

1.2.698

3 years ago

1.2.695

3 years ago

1.2.694

3 years ago

1.2.696

3 years ago

1.2.693

3 years ago

1.2.691

3 years ago

1.2.692

3 years ago

1.2.690

3 years ago

1.2.689

3 years ago

1.2.688

3 years ago

1.2.687

3 years ago

1.2.686

3 years ago

1.2.685

3 years ago

1.2.684

3 years ago

1.2.683

3 years ago

1.2.680

3 years ago

1.2.682

3 years ago

1.2.681

3 years ago

1.2.679

3 years ago

1.2.678

3 years ago

1.2.677

3 years ago

1.2.676

3 years ago

1.2.675

3 years ago

1.2.674

3 years ago

1.2.673

3 years ago

1.2.672

3 years ago

1.2.671

3 years ago

1.2.668

3 years ago

1.2.667

3 years ago

1.2.670

3 years ago

1.2.669

3 years ago

1.2.666

3 years ago

1.2.664

3 years ago

1.2.663

3 years ago

1.2.665

3 years ago

1.2.662

3 years ago

1.2.661

3 years ago

1.2.660

3 years ago

1.2.657

3 years ago

1.2.659

3 years ago

1.2.658

3 years ago

1.2.656

3 years ago

1.2.651

3 years ago

1.2.650

3 years ago

1.2.653

3 years ago

1.2.652

3 years ago

1.2.655

3 years ago

1.2.654

3 years ago

1.2.648

3 years ago

1.2.649

3 years ago

1.2.647

3 years ago

1.2.646

3 years ago

1.2.645

3 years ago

1.2.644

3 years ago

1.2.643

3 years ago

1.2.642

3 years ago

1.2.641

3 years ago

1.2.640

3 years ago

1.2.639

3 years ago

1.2.638

3 years ago

1.2.637

3 years ago

1.2.635

3 years ago

1.2.634

3 years ago

1.2.636

3 years ago

1.2.631

3 years ago

1.2.630

3 years ago

1.2.633

3 years ago

1.2.632

3 years ago

1.2.628

3 years ago

1.2.627

3 years ago

1.2.629

3 years ago

1.2.626

3 years ago

1.2.622

3 years ago

1.2.624

3 years ago

1.2.623

3 years ago

1.2.625

3 years ago

1.2.621

3 years ago

1.2.620

3 years ago

1.2.619

3 years ago

1.2.613

3 years ago

1.2.612

3 years ago

1.2.615

3 years ago

1.2.614

3 years ago

1.2.617

3 years ago

1.2.616

3 years ago

1.2.618

3 years ago

1.2.611

3 years ago

1.2.610

3 years ago

1.2.608

3 years ago

1.2.607

3 years ago

1.2.609

3 years ago

1.2.606

3 years ago

1.2.604

3 years ago

1.2.605

3 years ago

1.2.599

3 years ago

1.2.600

3 years ago

1.2.602

3 years ago

1.2.601

3 years ago

1.2.603

3 years ago

1.2.598

3 years ago

1.2.597

3 years ago

1.2.596

3 years ago

1.2.595

3 years ago

1.2.594

3 years ago

1.2.592

3 years ago

1.2.593

3 years ago

1.2.591

3 years ago

1.2.590

3 years ago

1.2.589

3 years ago

1.2.588

3 years ago

1.2.587

3 years ago

1.2.586

3 years ago

1.2.585

3 years ago

1.2.584

3 years ago

1.2.581

3 years ago

1.2.583

3 years ago

1.2.582

3 years ago

1.2.576

3 years ago

1.2.578

3 years ago

1.2.577

3 years ago

1.2.579

3 years ago

1.2.580

3 years ago

1.2.575

3 years ago

1.2.574

3 years ago

1.2.573

3 years ago

1.2.572

3 years ago

1.2.571

3 years ago

1.2.570

3 years ago

1.2.569

3 years ago

1.2.568

3 years ago

1.2.564

3 years ago

1.2.567

3 years ago

1.2.566

3 years ago

1.2.563

3 years ago

1.2.562

3 years ago

1.2.561

3 years ago

1.2.560

3 years ago

1.2.556

3 years ago

1.2.555

3 years ago

1.2.558

3 years ago

1.2.557

3 years ago

1.2.554

3 years ago

1.2.552

3 years ago

1.2.553

3 years ago

1.2.551

3 years ago

1.2.550

3 years ago

1.2.549

3 years ago

1.2.548

3 years ago

1.2.547

3 years ago

1.2.546

3 years ago

1.2.545

3 years ago

1.2.544

3 years ago

1.2.543

3 years ago

1.2.542

3 years ago

1.2.541

3 years ago

1.2.540

3 years ago

1.2.539

3 years ago

1.2.538

3 years ago

1.2.536

3 years ago

1.2.537

3 years ago

1.2.532

3 years ago

1.2.534

3 years ago

1.2.533

3 years ago

1.2.535

3 years ago

1.2.531

3 years ago

1.2.530

4 years ago

1.2.527

4 years ago

1.2.529

4 years ago

1.2.528

4 years ago

1.2.526

4 years ago

1.2.525

4 years ago

1.2.523

4 years ago

1.2.524

4 years ago

1.2.522

4 years ago

1.2.521

4 years ago

1.2.520

4 years ago

1.2.519

4 years ago

1.2.518

4 years ago

1.2.517

4 years ago

1.2.514

4 years ago

1.2.516

4 years ago

1.2.515

4 years ago

1.2.513

4 years ago

1.2.512

4 years ago

1.2.511

4 years ago

1.2.510

4 years ago

1.2.509

4 years ago

1.2.508

4 years ago

1.2.507

4 years ago

1.2.506

4 years ago

1.2.505

4 years ago

1.2.504

4 years ago

1.2.503

4 years ago

1.2.501

4 years ago

1.2.500

4 years ago

1.2.502

4 years ago

1.2.497

4 years ago

1.2.496

4 years ago

1.2.499

4 years ago

1.2.498

4 years ago

1.2.493

4 years ago

1.2.495

4 years ago

1.2.494

4 years ago

1.2.492

4 years ago

1.2.491

4 years ago

1.2.490

4 years ago

1.2.489

4 years ago

1.2.488

4 years ago

1.2.487

4 years ago

1.2.486

4 years ago

1.2.485

4 years ago

1.2.484

4 years ago

1.2.482

4 years ago

1.2.481

4 years ago

1.2.483

4 years ago

1.2.480

4 years ago

1.2.479

4 years ago

1.2.477

4 years ago

1.2.478

4 years ago

1.2.476

4 years ago

1.2.475

4 years ago

1.2.474

4 years ago

1.2.473

4 years ago

1.2.471

4 years ago

1.2.470

4 years ago

1.2.472

4 years ago

1.2.469

4 years ago

1.2.466

4 years ago

1.2.465

4 years ago

1.2.468

4 years ago

1.2.467

4 years ago

1.2.464

4 years ago

1.2.463

4 years ago

1.2.462

4 years ago

1.2.461

4 years ago

1.2.460

4 years ago

1.2.459

4 years ago

1.2.457

4 years ago

1.2.458

4 years ago

1.2.456

4 years ago

1.2.455

4 years ago

1.2.454

4 years ago

1.2.453

4 years ago

1.2.452

4 years ago

1.2.451

4 years ago

1.2.450

4 years ago

1.2.449

4 years ago

1.2.448

4 years ago

1.2.447

4 years ago

1.2.446

4 years ago

1.2.445

4 years ago

1.2.444

4 years ago

1.2.443

4 years ago

1.2.442

4 years ago

1.2.441

4 years ago

1.2.440

4 years ago

1.2.439

4 years ago

1.2.438

4 years ago

1.2.437

4 years ago

1.2.436

4 years ago

1.2.435

4 years ago

1.2.434

4 years ago

1.2.433

4 years ago

1.2.432

4 years ago

1.2.431

4 years ago

1.2.430

4 years ago

1.2.429

4 years ago

1.2.428

4 years ago

1.2.427

4 years ago

1.2.426

4 years ago

1.2.425

4 years ago

1.2.424

4 years ago

1.2.423

4 years ago

1.2.422

4 years ago

1.2.419

4 years ago

1.2.421

4 years ago

1.2.420

4 years ago

1.2.418

4 years ago

1.2.417

4 years ago

1.2.416

4 years ago

1.2.415

4 years ago

1.2.414

4 years ago

1.2.413

4 years ago

1.2.412

4 years ago

1.2.411

4 years ago

1.2.410

4 years ago

1.2.409

4 years ago

1.2.408

4 years ago

1.2.407

4 years ago

1.2.406

4 years ago

1.2.405

4 years ago

1.2.404

4 years ago

1.2.403

4 years ago

1.2.402

4 years ago

1.2.401

4 years ago

1.2.400

4 years ago

1.2.398

4 years ago

1.2.399

4 years ago

1.2.397

4 years ago

1.2.396

4 years ago

1.2.394

4 years ago

1.2.395

4 years ago

1.2.392

4 years ago

1.2.393

4 years ago

1.2.390

4 years ago

1.2.391

4 years ago

1.2.389

4 years ago

1.2.388

4 years ago

1.2.387

4 years ago

1.2.386

4 years ago

1.2.385

4 years ago

1.2.384

4 years ago

1.2.383

4 years ago

1.2.382

4 years ago

1.2.381

4 years ago

1.2.380

4 years ago

1.2.379

4 years ago

1.2.378

4 years ago

1.2.377

4 years ago

1.2.376

4 years ago

1.2.375

4 years ago

1.2.374

4 years ago

1.2.373

4 years ago

1.2.372

4 years ago

1.2.371

4 years ago

1.2.370

4 years ago

1.2.369

4 years ago

1.2.368

4 years ago

1.2.367

4 years ago

1.2.366

4 years ago

1.2.365

4 years ago

1.2.364

4 years ago

1.2.361

4 years ago

1.2.363

4 years ago

1.2.362

4 years ago

1.2.358

4 years ago

1.2.359

4 years ago

1.2.360

4 years ago

1.2.356

4 years ago

1.2.355

4 years ago

1.2.357

4 years ago

1.2.354

4 years ago

1.2.353

4 years ago

1.2.352

4 years ago

1.2.351

4 years ago

1.2.350

4 years ago

1.2.349

4 years ago

1.2.348

4 years ago

1.2.347

4 years ago

1.2.346

4 years ago

1.2.343

4 years ago

1.2.345

4 years ago

1.2.344

4 years ago

1.2.342

4 years ago

1.2.341

4 years ago

1.2.340

4 years ago

1.2.338

4 years ago

1.2.339

4 years ago

1.2.337

4 years ago

1.2.336

4 years ago

1.2.335

4 years ago

1.2.334

4 years ago

1.2.333

4 years ago

1.2.332

4 years ago

1.2.331

4 years ago

1.2.330

4 years ago

1.2.329

4 years ago

1.2.328

4 years ago

1.2.327

4 years ago

1.2.326

4 years ago

1.2.325

4 years ago

1.2.324

4 years ago

1.2.323

4 years ago

1.2.310

4 years ago

1.2.312

4 years ago

1.2.311

4 years ago

1.2.314

4 years ago

1.2.313

4 years ago

1.2.316

4 years ago

1.2.315

4 years ago

1.2.321

4 years ago

1.2.320

4 years ago

1.2.322

4 years ago

1.2.318

4 years ago

1.2.317

4 years ago

1.2.319

4 years ago

1.2.309

4 years ago

1.2.308

4 years ago

1.2.305

4 years ago

1.2.307

4 years ago

1.2.306

4 years ago

1.2.304

4 years ago

1.2.303

4 years ago

1.2.302

4 years ago

1.2.301

4 years ago

1.2.300

4 years ago

1.2.299

4 years ago

1.2.298

4 years ago

1.2.297

4 years ago

1.2.293

4 years ago

1.2.295

4 years ago

1.2.294

4 years ago

1.2.296

4 years ago

1.2.292

4 years ago

1.2.291

4 years ago

1.2.289

4 years ago

1.2.290

4 years ago

1.2.288

4 years ago

1.2.287

4 years ago

1.2.286

4 years ago

1.2.285

4 years ago

1.2.284

4 years ago

1.2.283

4 years ago

1.2.280

4 years ago

1.2.282

4 years ago

1.2.281

4 years ago

1.2.279

4 years ago

1.2.278

4 years ago

1.2.277

4 years ago

1.2.276

4 years ago

1.2.275

4 years ago

1.2.274

4 years ago

1.2.273

4 years ago

1.2.272

4 years ago

1.2.271

4 years ago

1.2.269

4 years ago

1.2.270

4 years ago

1.2.268

4 years ago

1.2.267

4 years ago

1.2.266

4 years ago

1.2.265

4 years ago

1.2.264

4 years ago

1.2.263

4 years ago

1.2.262

4 years ago

1.2.261

4 years ago

1.2.260

4 years ago

1.2.259

4 years ago

1.2.257

4 years ago

1.2.258

4 years ago

1.2.255

4 years ago

1.2.256

4 years ago

1.2.254

4 years ago

1.2.253

4 years ago

1.2.252

4 years ago

1.2.251

4 years ago

1.2.250

4 years ago

1.2.249

4 years ago

1.2.248

4 years ago

1.2.247

4 years ago

1.2.246

4 years ago

1.2.245

4 years ago

1.2.244

4 years ago

1.2.243

4 years ago

1.2.242

4 years ago

1.2.239

4 years ago

1.2.240

4 years ago

1.2.241

4 years ago

1.2.237

4 years ago

1.2.238

4 years ago

1.2.236

4 years ago

1.2.235

4 years ago

1.2.233

4 years ago

1.2.234

4 years ago

1.2.232

4 years ago

1.2.231

4 years ago

1.2.230

4 years ago

1.2.229

4 years ago

1.2.228

4 years ago

1.2.227

4 years ago

1.2.222

4 years ago

1.2.224

4 years ago

1.2.223

4 years ago

1.2.226

4 years ago

1.2.225

4 years ago

1.2.221

4 years ago

1.2.220

4 years ago

1.2.219

4 years ago

1.2.218

4 years ago

1.2.217

4 years ago

1.2.216

4 years ago

1.2.215

4 years ago

1.2.214

4 years ago

1.2.213

4 years ago

1.2.212

4 years ago

1.2.211

4 years ago

1.2.210

4 years ago

1.2.209

4 years ago

1.2.208

4 years ago

1.2.207

4 years ago

1.2.206

4 years ago

1.2.200

4 years ago

1.2.202

4 years ago

1.2.201

4 years ago

1.2.204

4 years ago

1.2.203

4 years ago

1.2.205

4 years ago

1.2.199

4 years ago

1.2.198

4 years ago

1.2.197

4 years ago

1.2.192

4 years ago

1.2.194

4 years ago

1.2.193

4 years ago

1.2.196

4 years ago

1.2.195

4 years ago

1.2.191

4 years ago

1.2.190

4 years ago

1.2.189

4 years ago

1.2.188

4 years ago

1.2.187

4 years ago

1.2.186

4 years ago

1.2.185

4 years ago

1.2.184

4 years ago

1.2.183

4 years ago

1.2.181

4 years ago

1.2.180

4 years ago

1.2.182

4 years ago

1.2.178

4 years ago

1.2.177

4 years ago

1.2.179

4 years ago

1.2.176

4 years ago

1.2.174

4 years ago

1.2.175

4 years ago

1.2.173

4 years ago

1.2.172

4 years ago

1.2.170

4 years ago

1.2.171

4 years ago

1.2.169

4 years ago

1.2.168

4 years ago

1.2.167

4 years ago

1.2.166

4 years ago

1.2.165

4 years ago

1.2.164

4 years ago

1.2.163

4 years ago

1.2.162

4 years ago

1.2.161

4 years ago

1.2.160

4 years ago

1.2.159

4 years ago

1.2.158

4 years ago

1.2.157

4 years ago

1.2.156

4 years ago

1.2.155

4 years ago

1.2.154

4 years ago

1.2.152

4 years ago

1.2.151

4 years ago

1.2.153

4 years ago

1.2.150

4 years ago

1.2.147

4 years ago

1.2.149

4 years ago

1.2.148

4 years ago

1.2.145

4 years ago

1.2.146

4 years ago

1.2.144

4 years ago

1.2.143

4 years ago

1.2.142

4 years ago

1.2.139

4 years ago

1.2.141

4 years ago

1.2.140

4 years ago

1.2.138

4 years ago

1.2.136

4 years ago

1.2.137

4 years ago

1.2.135

4 years ago

1.2.134

4 years ago

1.2.133

4 years ago

1.2.132

4 years ago

1.2.130

4 years ago

1.2.131

4 years ago

1.2.127

4 years ago

1.2.126

4 years ago

1.2.129

4 years ago

1.2.128

4 years ago

1.2.123

4 years ago

1.2.122

4 years ago

1.2.125

4 years ago

1.2.124

4 years ago

1.2.121

4 years ago

1.2.120

4 years ago

1.2.119

4 years ago

1.2.118

4 years ago

1.2.117

4 years ago

1.2.116

4 years ago

1.2.115

4 years ago

1.2.114

4 years ago

1.2.113

4 years ago

1.2.112

4 years ago

1.2.111

4 years ago

1.2.110

4 years ago

1.2.109

4 years ago

1.2.108

4 years ago

1.2.105

4 years ago

1.2.107

4 years ago

1.2.106

4 years ago

1.2.104

4 years ago

1.2.101

4 years ago

1.2.103

4 years ago

1.2.102

4 years ago

1.2.100

4 years ago

1.2.99

4 years ago

1.2.96

4 years ago

1.2.97

4 years ago

1.2.98

4 years ago

1.2.95

4 years ago

1.2.93

4 years ago

1.2.94

4 years ago

1.2.92

4 years ago

1.2.91

4 years ago

1.2.90

4 years ago

1.2.89

4 years ago

1.2.87

4 years ago

1.2.88

4 years ago

1.2.86

4 years ago

1.2.85

4 years ago

1.2.84

4 years ago

1.2.83

4 years ago

1.2.81

4 years ago

1.2.82

4 years ago

1.2.79

4 years ago

1.2.78

4 years ago

1.2.77

4 years ago

1.2.75

4 years ago

1.2.76

4 years ago

1.2.74

4 years ago

1.2.73

4 years ago

1.2.72

4 years ago

1.2.68

4 years ago

1.2.69

4 years ago

1.2.70

4 years ago

1.2.71

4 years ago

1.2.67

4 years ago

1.2.65

4 years ago

1.2.66

4 years ago

1.2.64

4 years ago

1.2.63

4 years ago

1.2.61

4 years ago

1.2.62

4 years ago

1.2.60

4 years ago

1.2.59

4 years ago

1.2.57

4 years ago

1.2.58

4 years ago

1.2.56

4 years ago

1.2.55

4 years ago

1.2.54

4 years ago

1.2.53

4 years ago

1.2.52

4 years ago

1.2.51

4 years ago

1.2.49

4 years ago

1.2.50

4 years ago

1.2.45

4 years ago

1.2.46

4 years ago

1.2.47

4 years ago

1.2.48

4 years ago

1.2.44

4 years ago

1.2.43

4 years ago

1.2.42

4 years ago

1.2.41

4 years ago

1.2.40

4 years ago

1.2.34

4 years ago

1.2.35

4 years ago

1.2.38

4 years ago

1.2.39

4 years ago

1.2.36

4 years ago

1.2.37

4 years ago

1.2.33

4 years ago

1.2.32

4 years ago

1.2.31

4 years ago

1.2.30

4 years ago

1.2.29

4 years ago

1.2.27

4 years ago

1.2.28

4 years ago

1.2.26

4 years ago

1.2.25

4 years ago

1.2.24

4 years ago

1.2.23

4 years ago

1.2.22

4 years ago

1.2.21

4 years ago

1.2.19

4 years ago

1.2.20

4 years ago

1.2.18

4 years ago

1.2.17

4 years ago

1.2.16

4 years ago

1.2.15

4 years ago

1.2.14

4 years ago

1.2.13

4 years ago

1.2.12

4 years ago

1.2.10

4 years ago

1.2.11

4 years ago

1.2.9

4 years ago

1.2.8

4 years ago

1.2.7

4 years ago

1.2.6

4 years ago

1.2.5

4 years ago

1.2.4

4 years ago

1.2.3

4 years ago

1.2.2

4 years ago

1.2.1

4 years ago

1.2.0

4 years ago

1.1.1

4 years ago

1.1.0

4 years ago

1.0.40

4 years ago

1.0.39

4 years ago

1.0.38

4 years ago

1.0.37

4 years ago

1.0.36

5 years ago

1.0.35

5 years ago

1.0.34

5 years ago

1.0.33

5 years ago

1.0.32

5 years ago

1.0.31

5 years ago

1.0.30

5 years ago

1.0.29

5 years ago

1.0.28

5 years ago

1.0.27

5 years ago

1.0.26

5 years ago

1.0.25

5 years ago

1.0.24

5 years ago

1.0.23

5 years ago

1.0.22

5 years ago

1.0.21

5 years ago

1.0.20

5 years ago

1.0.19

5 years ago

1.0.18

5 years ago

1.0.17

5 years ago

1.0.16

5 years ago

1.0.15

5 years ago

1.0.14

5 years ago

1.0.13

5 years ago

1.0.12

5 years ago

1.0.11

5 years ago

1.0.10

5 years ago

1.0.9

5 years ago

1.0.8

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.41

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago