Merge pull request #2662 from crazyserver/MOBILE-3626

Mobile 3626
main
Dani Palou 2021-01-20 11:27:06 +01:00 committed by GitHub
commit fa7fa69ae0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
104 changed files with 13037 additions and 60 deletions

View File

@ -40,10 +40,12 @@ import { AddonPrivateFilesModule } from './privatefiles/privatefiles.module';
import { AddonFilterModule } from './filter/filter.module';
import { AddonUserProfileFieldModule } from './userprofilefield/userprofilefield.module';
import { AddonBadgesModule } from './badges/badges.module';
import { AddonCalendarModule } from './calendar/calendar.module';
@NgModule({
imports: [
AddonBadgesModule,
AddonCalendarModule,
AddonPrivateFilesModule,
AddonFilterModule,
AddonBlockActivityResultsModule,

View File

@ -24,7 +24,7 @@ import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-ro
import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
import { AddonBadgesPushClickHandler } from './services/handlers/push-click';
const mainMenuHomeSiblingRoutes: Routes = [
const mainMenuRoutes: Routes = [
{
path: 'badges',
loadChildren: () => import('./badges-lazy.module').then(m => m.AddonBadgesLazyModule),
@ -33,7 +33,7 @@ const mainMenuHomeSiblingRoutes: Routes = [
@NgModule({
imports: [
CoreMainMenuTabRoutingModule.forChild(mainMenuHomeSiblingRoutes),
CoreMainMenuTabRoutingModule.forChild(mainMenuRoutes),
],
providers: [
{

View File

@ -50,7 +50,7 @@ export class AddonBadgesIssuedBadgePage implements OnInit {
* View loaded.
*/
ngOnInit(): void {
this.courseId = this.route.snapshot.queryParams['courseId'] || this.courseId; // Use 0 for site badges.
this.courseId = parseInt(this.route.snapshot.queryParams['courseId'], 10) || this.courseId; // Use 0 for site badges.
this.userId = this.route.snapshot.queryParams['userId'] ||
CoreSites.instance.getCurrentSite()?.getUserId();
this.badgeHash = this.route.snapshot.queryParams['badgeHash'];

View File

@ -51,7 +51,7 @@ export class AddonBadgesUserBadgesPage implements OnInit {
*/
ngOnInit(): void {
this.courseId = this.route.snapshot.queryParams['courseId'] || this.courseId; // Use 0 for site badges.
this.courseId = parseInt(this.route.snapshot.queryParams['courseId'], 10) || this.courseId; // Use 0 for site badges.
this.userId = this.route.snapshot.queryParams['userId'] ||
CoreSites.instance.getCurrentSite()?.getUserId();

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
// import { AddonCalendar } from '@addon/calendar/services/calendar';
import { AddonCalendar } from '@/addons/calendar/services/calendar';
import { CoreCourseBlock } from '@features/course/services/course';
import { Params } from '@angular/router';
import { makeSingleton } from '@singletons';
@ -39,19 +39,13 @@ export class AddonBlockCalendarMonthHandlerService extends CoreBlockBaseHandler
* @return Data or promise resolved with the data.
*/
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
// @todo
const link = 'AddonCalendarListPage';
const linkParams: Params = contextLevel == 'course' ? { courseId: instanceId } : {};
/* if (AddonCalendar.instance.canViewMonthInSite()) {
link = 'AddonCalendarIndexPage';
}*/
return {
title: 'addon.block_calendarmonth.pluginname',
class: 'addon-block-calendar-month',
component: CoreBlockOnlyTitleComponent,
link: link,
link: AddonCalendar.instance.getMainCalendarPagePath(),
linkParams: linkParams,
};
}

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
// import { AddonCalendar } from '@addon/calendar/services/calendar';
import { AddonCalendar } from '@/addons/calendar/services/calendar';
import { CoreCourseBlock } from '@features/course/services/course';
import { Params } from '@angular/router';
import { makeSingleton } from '@singletons';
@ -39,20 +39,13 @@ export class AddonBlockCalendarUpcomingHandlerService extends CoreBlockBaseHandl
* @return Data or promise resolved with the data.
*/
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
// @todo
const link = 'AddonCalendarListPage';
const linkParams: Params = contextLevel == 'course' ? { courseId: instanceId } : {};
/* if (AddonCalendar.instance.canViewMonthInSite()) {
link = 'AddonCalendarIndexPage';
linkParams.upcoming = true;
}*/
return {
title: 'addon.block_calendarupcoming.pluginname',
class: 'addon-block-calendar-upcoming',
component: CoreBlockOnlyTitleComponent,
link: link,
link: AddonCalendar.instance.getMainCalendarPagePath(),
linkParams: linkParams,
};
}

View File

@ -45,7 +45,7 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl
* Component being initialized.
*/
async ngOnInit(): Promise<void> {
this.siteHomeId = CoreSites.instance.getCurrentSite()?.getSiteHomeId() || 1;
this.siteHomeId = CoreSites.instance.getCurrentSiteHomeId();
super.ngOnInit();
}

View File

@ -0,0 +1,28 @@
:host {
--addon-calendar-blank-day-background-color: var(--gray-lighter);
.item.addon-calendar-event {
> ion-icon {
color: white;
border-radius: 50%;
padding: 6px;
}
&.addon-calendar-eventtype-category > ion-icon {
background-color: var(--addon-calendar-event-category-color);
}
&.addon-calendar-eventtype-course > ion-icon {
background-color: var(--addon-calendar-event-course-color);
}
&.addon-calendar-eventtype-group > ion-icon {
background-color: var(--addon-calendar-event-group-color);
}
&.addon-calendar-eventtype-user > ion-icon {
background-color: var(--addon-calendar-event-user-color);
}
&.addon-calendar-eventtype-site > ion-icon {
background-color: var(--addon-calendar-event-site-color);
}
}
}

View File

@ -0,0 +1,68 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injector, NgModule } from '@angular/core';
import { RouterModule, ROUTES, Routes } from '@angular/router';
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
function buildRoutes(injector: Injector): Routes {
return [
{
path: 'index',
loadChildren: () => import('@/addons/calendar/pages/index/index.module').then(m => m.AddonCalendarIndexPageModule),
},
{
path: 'list',
loadChildren: () => import('@/addons/calendar/pages/list/list.module').then(m => m.AddonCalendarListPageModule),
},
{
path: 'settings',
loadChildren: () =>
import('@/addons/calendar/pages/settings/settings.module').then(m => m.AddonCalendarSettingsPageModule),
},
{
path: 'day',
loadChildren: () =>
import('@/addons/calendar/pages/day/day.module').then(m => m.AddonCalendarDayPageModule),
},
{
path: 'event',
loadChildren: () =>
import('@/addons/calendar/pages/event/event.module').then(m => m.AddonCalendarEventPageModule),
},
{
path: 'edit',
loadChildren: () =>
import('@/addons/calendar/pages/edit-event/edit-event.module').then(m => m.AddonCalendarEditEventPageModule),
},
...buildTabMainRoutes(injector, {
redirectTo: 'index',
pathMatch: 'full',
}),
];
}
@NgModule({
exports: [RouterModule],
providers: [
{
provide: ROUTES,
multi: true,
deps: [Injector],
useFactory: buildRoutes,
},
],
})
export class AddonCalendarLazyModule { }

View File

@ -0,0 +1,67 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { Routes } from '@angular/router';
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
import { CoreCronDelegate } from '@services/cron';
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
import { AddonCalendarViewLinkHandler } from './services/handlers/view-link';
import { AddonCalendarMainMenuHandler, AddonCalendarMainMenuHandlerService } from './services/handlers/mainmenu';
import { AddonCalendarSyncCronHandler } from './services/handlers/sync-cron';
import { CORE_SITE_SCHEMAS } from '@services/sites';
import { CALENDAR_SITE_SCHEMA } from './services/database/calendar';
import { CALENDAR_OFFLINE_SITE_SCHEMA } from './services/database/calendar-offline';
import { AddonCalendarComponentsModule } from './components/components.module';
import { AddonCalendar } from './services/calendar';
const mainMenuChildrenRoutes: Routes = [
{
path: AddonCalendarMainMenuHandlerService.PAGE_NAME,
loadChildren: () => import('./calendar-lazy.module').then(m => m.AddonCalendarLazyModule),
},
];
@NgModule({
imports: [
CoreMainMenuRoutingModule.forChild({ children: mainMenuChildrenRoutes }),
AddonCalendarComponentsModule,
],
exports: [CoreMainMenuRoutingModule],
providers: [
{
provide: CORE_SITE_SCHEMAS,
useValue: [CALENDAR_SITE_SCHEMA, CALENDAR_OFFLINE_SITE_SCHEMA],
multi: true,
},
{
provide: APP_INITIALIZER,
multi: true,
deps: [],
useFactory: () => async () => {
CoreContentLinksDelegate.instance.registerHandler(AddonCalendarViewLinkHandler.instance);
CoreMainMenuDelegate.instance.registerHandler(AddonCalendarMainMenuHandler.instance);
CoreCronDelegate.instance.register(AddonCalendarSyncCronHandler.instance);
await AddonCalendar.instance.initialize();
AddonCalendar.instance.scheduleAllSitesEventsNotifications();
},
},
],
})
export class AddonCalendarModule {}

View File

@ -0,0 +1,79 @@
<!-- Add buttons to the nav bar. -->
<core-navbar-buttons slot="end" prepend>
<core-context-menu>
<core-context-menu-item *ngIf="canNavigate && !isCurrentMonth && displayNavButtons" [priority]="900"
[content]="'addon.calendar.currentmonth' | translate" iconAction="fas-calendar-day"
(action)="goToCurrentMonth()"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
<!-- Period name and arrows to navigate. -->
<ion-grid class="ion-no-padding addon-calendar-navigation">
<ion-row class="ion-align-items-center">
<ion-col class="ion-text-start" *ngIf="canNavigate">
<ion-button fill="clear" (click)="loadPrevious()" [title]="'core.previous' | translate">
<ion-icon name="fas-chevron-left" slot="icon-only"></ion-icon>
</ion-button>
</ion-col>
<ion-col class="ion-text-center addon-calendar-period">
<h3>{{ periodName }}</h3>
</ion-col>
<ion-col class="ion-text-end" *ngIf="canNavigate">
<ion-button fill="clear" (click)="loadNext()" [title]="'core.next' | translate">
<ion-icon name="fas-chevron-right" slot="icon-only"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
<!-- Calendar view. -->
<ion-grid class="addon-calendar-months">
<!-- List of days. -->
<ion-row>
<ion-col class="ion-text-center" *ngFor="let day of weekDays" class="addon-calendar-weekday">
<span class="ion-hide-md-up" [title]="day.fullname | translate">{{ day.shortname | translate }}</span>
<span class="ion-hide-md-down">{{ day.fullname | translate }}</span>
</ion-col>
</ion-row>
<!-- Weeks. -->
<ion-row *ngFor="let week of weeks" class="addon-calendar-week">
<!-- Empty slots (first week). -->
<ion-col *ngFor="let value of week.prepadding" class="dayblank addon-calendar-day"></ion-col>
<ion-col class="ion-text-center" *ngFor="let day of week.days" (click)="dayClicked(day.mday)"
[ngClass]='{"hasevents": day.hasevents, "today": isCurrentMonth && day.istoday,
"weekend": day.isweekend, "duration_finish": day.haslastdayofevent}'
class="addon-calendar-day" [class.addon-calendar-event-past-day]="isPastMonth || day.ispast">
<p class="addon-calendar-day-number"><span>{{ day.mday }}</span></p>
<!-- In phone, display some dots to indicate the type of events. -->
<p class="ion-hide-md-up addon-calendar-dot-types"><span *ngFor="let type of day.calendareventtypes"
class="calendar_event_type calendar_event_{{type}}"></span></p>
<!-- In tablet, display list of events. -->
<div class="ion-hide-md-down addon-calendar-day-events">
<ng-container *ngFor="let event of day.filteredEvents | slice:0:4; let index = index">
<p *ngIf="index < 3 || day.filteredEvents.length == 4" class="addon-calendar-event"
(click)="eventClicked(event, $event)" [class.addon-calendar-event-past]="event.ispast">
<span class="calendar_event_type calendar_event_{{event.formattedType}}"></span>
<ion-icon *ngIf="event.offline && !event.deleted" name="far-clock"></ion-icon>
<ion-icon *ngIf="event.deleted" name="fas-trash"></ion-icon>
<span class="addon-calendar-event-time">{{ event.timestart * 1000 | coreFormatDate: timeFormat }}</span>
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation"
class="core-module-icon">
<span class="addon-calendar-event-name">{{event.name}}</span>
</p>
</ng-container>
<p *ngIf="day.filteredEvents.length > 4" class="addon-calendar-day-more">
<b>{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}</b>
</p>
</div>
</ion-col>
<!-- Empty slots (last week). -->
<ion-col *ngFor="let value of week.postpadding" class="dayblank addon-calendar-day"></ion-col>
</ion-row>
</ion-grid>
</core-loading>

View File

@ -0,0 +1,187 @@
:host {
--addon-calendar-blank-day-background-color: var(--gray-lighter);
.addon-calendar-navigation {
padding-top: 5px;
padding-left: 10px;
padding-right: 10px;
}
.addon-calendar-months {
background-color: var(--contrast-background);
padding: 0;
}
.addon-calendar-day {
border-bottom: 1px solid var(--addon-calendar-border-color);
border-right: 1px solid var(--addon-calendar-border-color);
overflow: hidden;
min-height: 60px;
cursor: pointer;
&:first-child {
padding-left: 10px;
}
&:last-child {
border-right: 0;
padding-left: 8px;
}
&.addon-calendar-event-past-day > .addon-calendar-dot-types,
&.addon-calendar-event-past-day > .addon-calendar-day-events {
opacity: 0.5;
}
.addon-calendar-day-number {
margin: 0;
span {
line-height: 24px;
font-weight: 500;
display: inline-block;
margin: 3px;
width: max-content;
width: 24px;
height: 24px;
text-align: center;
}
}
@media (min-width: 768px) {
.addon-calendar-day-number {
text-align: start;
}
}
&.today .addon-calendar-day-number span {
background-color: var(--addon-calendar-today-bgcolor);
color: var(--addon-calendar-today-color);
border-radius: 50%;
}
&.dayblank {
cursor: auto;
background-color: var(--addon-calendar-blank-day-background-color);
}
.addon-calendar-event {
margin-top: 0.6em;
margin-bottom: 0.6em;
overflow: hidden;
white-space: nowrap;
&.addon-calendar-event-past {
opacity: 0.5;
}
.addon-calendar-event-name {
font-weight: 500;
}
}
.addon-calendar-day-more {
margin-top: 0.6em;
margin-bottom: 0.6em;
margin-right: 4px;
}
.addon-calendar-dot-types {
margin: 0;
}
}
.addon-calendar-period {
flex-grow: 3;
h3 {
margin-top: 10px;
font-size: 1.2rem;
}
}
.addon-calendar-weekday {
border-bottom: 1px solid var(--addon-calendar-border-color);
}
.addon-calendar-day-events {
text-align: left;
ion-icon {
margin-right: 2px;
font-size: 1em;
}
}
.addon-calendar-event, .addon-calendar-day-number, .addon-calendar-day-more {
cursor: pointer;
}
.calendar_event_type {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
border: 1px solid white;
margin-right: 1px;
margin-left: 1px;
&.calendar_event_category {
background-color: var(--addon-calendar-event-category-color);
}
&.calendar_event_course {
background-color: var(--addon-calendar-event-course-color);
}
&.calendar_event_group {
background-color: var(--addon-calendar-event-group-color);
}
&.calendar_event_user {
background-color: var(--addon-calendar-event-user-color);
}
&.calendar_event_site {
background-color: var(--addon-calendar-event-site-color);
}
}
.core-module-icon {
margin-right: 1px;
margin-left: 1px;
width: 16px;
height: 16px;
display: inline-block;
vertical-align: bottom;
}
}
:host-context([dir=rtl]) {
.addon-calendar-day-events {
text-align: right;
ion-icon {
margin-right: unset;
margin-left: 2px;
}
}
.addon-calendar-day {
border-left: 1px solid var(--addon-calendar-border-color);
border-right: unset;
&:first-child {
padding-right: 10px;
padding-left: unset;
}
&:last-child {
border-left: 0;
border-right: unset;
padding-right: 8px;
padding-left: unset;
}
.addon-calendar-day-more {
margin-left: 4px;
margin-right: unset;
}
}
}
:host-context(body.dark) {
--addon-calendar-blank-day-background-color: var(--black);
}

View File

@ -0,0 +1,529 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {
Component,
OnDestroy,
OnInit,
Input,
DoCheck,
Output,
EventEmitter,
KeyValueDiffers,
KeyValueDiffer,
} from '@angular/core';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import {
AddonCalendar,
AddonCalendarProvider,
AddonCalendarWeek,
AddonCalendarWeekDaysTranslationKeys,
AddonCalendarEventToDisplay,
AddonCalendarUpdatedEventEvent,
AddonCalendarDayName,
} from '../../services/calendar';
import { AddonCalendarFilter, AddonCalendarHelper } from '../../services/calendar-helper';
import { AddonCalendarOffline } from '../../services/calendar-offline';
import { CoreCategoryData, CoreCourses } from '@features/courses/services/courses';
import { CoreApp } from '@services/app';
import { CoreLocalNotifications } from '@services/local-notifications';
/**
* Component that displays a calendar.
*/
@Component({
selector: 'addon-calendar-calendar',
templateUrl: 'addon-calendar-calendar.html',
styleUrls: ['calendar.scss'],
})
export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestroy {
@Input() initialYear?: number; // Initial year to load.
@Input() initialMonth?: number; // Initial month to load.
@Input() filter?: AddonCalendarFilter; // Filter to apply.
@Input() canNavigate?: string | boolean; // Whether to include arrows to change the month. Defaults to true.
@Input() displayNavButtons?: string | boolean; // Whether to display nav buttons created by this component. Defaults to true.
@Output() onEventClicked = new EventEmitter<number>();
@Output() onDayClicked = new EventEmitter<{day: number; month: number; year: number}>();
periodName?: string;
weekDays: AddonCalendarWeekDaysTranslationKeys[] = [];
weeks: AddonCalendarWeek[] = [];
loaded = false;
timeFormat?: string;
isCurrentMonth = false;
isPastMonth = false;
protected year?: number;
protected month?: number;
protected categoriesRetrieved = false;
protected categories: { [id: number]: CoreCategoryData } = {};
protected currentSiteId: string;
protected offlineEvents: { [monthId: string]: { [day: number]: AddonCalendarEventToDisplay[] } } =
{}; // Offline events classified in month & day.
protected offlineEditedEventsIds: number[] = []; // IDs of events edited in offline.
protected deletedEvents: number[] = []; // Events deleted in offline.
protected currentTime?: number;
protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input.
// Observers.
protected undeleteEventObserver: CoreEventObserver;
protected obsDefaultTimeChange?: CoreEventObserver;
constructor(
differs: KeyValueDiffers,
) {
this.currentSiteId = CoreSites.instance.getCurrentSiteId();
if (CoreLocalNotifications.instance.isAvailable()) {
// Re-schedule events if default time changes.
this.obsDefaultTimeChange = CoreEvents.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
this.weeks.forEach((week) => {
week.days.forEach((day) => {
AddonCalendar.instance.scheduleEventsNotifications(day.eventsFormated!);
});
});
}, this.currentSiteId);
}
// Listen for events "undeleted" (offline).
this.undeleteEventObserver = CoreEvents.on(
AddonCalendarProvider.UNDELETED_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (!data || !data.eventId) {
return;
}
// Mark it as undeleted, no need to refresh.
this.undeleteEvent(data.eventId);
// Remove it from the list of deleted events if it's there.
const index = this.deletedEvents.indexOf(data.eventId);
if (index != -1) {
this.deletedEvents.splice(index, 1);
}
},
this.currentSiteId,
);
this.differ = differs.find([]).create();
}
/**
* Component loaded.
*/
ngOnInit(): void {
const now = new Date();
this.year = this.initialYear ? this.initialYear : now.getFullYear();
this.month = this.initialMonth ? this.initialMonth : now.getMonth() + 1;
this.calculateIsCurrentMonth();
this.fetchData();
}
/**
* Detect and act upon changes that Angular cant or wont detect on its own (objects and arrays).
*/
ngDoCheck(): void {
this.canNavigate = typeof this.canNavigate == 'undefined' ? true : CoreUtils.instance.isTrueOrOne(this.canNavigate);
this.displayNavButtons = typeof this.displayNavButtons == 'undefined' ? true :
CoreUtils.instance.isTrueOrOne(this.displayNavButtons);
if (this.weeks) {
// Check if there's any change in the filter object.
const changes = this.differ.diff(this.filter!);
if (changes) {
this.filterEvents();
}
}
}
/**
* Fetch contacts.
*
* @return Promise resolved when done.
*/
async fetchData(): Promise<void> {
const promises: Promise<void>[] = [];
promises.push(this.loadCategories());
// Get offline events.
promises.push(AddonCalendarOffline.instance.getAllEditedEvents().then((events) => {
// Classify them by month.
this.offlineEvents = AddonCalendarHelper.instance.classifyIntoMonths(events);
// Get the IDs of events edited in offline.
const filtered = events.filter((event) => event.id! > 0);
this.offlineEditedEventsIds = filtered.map((event) => event.id!);
return;
}));
// Get events deleted in offline.
promises.push(AddonCalendarOffline.instance.getAllDeletedEventsIds().then((ids) => {
this.deletedEvents = ids;
return;
}));
// Get time format to use.
promises.push(AddonCalendar.instance.getCalendarTimeFormat().then((value) => {
this.timeFormat = value;
return;
}));
try {
await Promise.all(promises);
await this.fetchEvents();
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}
this.loaded = true;
}
/**
* Fetch the events for current month.
*
* @return Promise resolved when done.
*/
async fetchEvents(): Promise<void> {
// Don't pass courseId and categoryId, we'll filter them locally.
let result: { daynames: Partial<AddonCalendarDayName>[]; weeks: Partial<AddonCalendarWeek>[] };
try {
result = await AddonCalendar.instance.getMonthlyEvents(this.year!, this.month!);
} catch (error) {
if (!CoreApp.instance.isOnline()) {
// Allow navigating to non-cached months in offline (behave as if using emergency cache).
result = await AddonCalendarHelper.instance.getOfflineMonthWeeks(this.year!, this.month!);
} else {
throw error;
}
}
// Calculate the period name. We don't use the one in result because it's in server's language.
this.periodName = CoreTimeUtils.instance.userDate(
new Date(this.year!, this.month! - 1).getTime(),
'core.strftimemonthyear',
);
this.weekDays = AddonCalendar.instance.getWeekDays(result.daynames[0].dayno);
this.weeks = result.weeks as AddonCalendarWeek[];
this.calculateIsCurrentMonth();
this.weeks.forEach((week) => {
week.days.forEach((day) => {
day.eventsFormated = day.eventsFormated || [];
day.filteredEvents = day.filteredEvents || [];
day.events.forEach((event) => {
/// Format online events.
day.eventsFormated!.push(AddonCalendarHelper.instance.formatEventData(event));
});
});
});
if (this.isCurrentMonth) {
const currentDay = new Date().getDate();
let isPast = true;
this.weeks.forEach((week) => {
week.days.forEach((day) => {
day.istoday = day.mday == currentDay;
day.ispast = isPast && !day.istoday;
isPast = day.ispast;
if (day.istoday) {
day.eventsFormated!.forEach((event) => {
event.ispast = this.isEventPast(event);
});
}
});
});
}
// Merge the online events with offline data.
this.mergeEvents();
// Filter events by course.
this.filterEvents();
}
/**
* Load categories to be able to filter events.
*
* @return Promise resolved when done.
*/
protected async loadCategories(): Promise<void> {
if (this.categoriesRetrieved) {
// Already retrieved, stop.
return;
}
try {
const cats = await CoreCourses.instance.getCategories(0, true);
this.categoriesRetrieved = true;
this.categories = {};
// Index categories by ID.
cats.forEach((category) => {
this.categories[category.id] = category;
});
} catch {
// Ignore errors.
}
}
/**
* Filter events based on the filter popover.
*/
filterEvents(): void {
this.weeks.forEach((week) => {
week.days.forEach((day) => {
day.filteredEvents = AddonCalendarHelper.instance.getFilteredEvents(
day.eventsFormated!,
this.filter!,
this.categories,
);
// Re-calculate some properties.
AddonCalendarHelper.instance.calculateDayData(day, day.filteredEvents);
});
});
}
/**
* Refresh events.
*
* @param afterChange Whether the refresh is done after an event has changed or has been synced.
* @return Promise resolved when done.
*/
async refreshData(afterChange?: boolean): Promise<void> {
const promises: Promise<void>[] = [];
// Don't invalidate monthly events after a change, it has already been handled.
if (!afterChange) {
promises.push(AddonCalendar.instance.invalidateMonthlyEvents(this.year!, this.month!));
}
promises.push(CoreCourses.instance.invalidateCategories(0, true));
promises.push(AddonCalendar.instance.invalidateTimeFormat());
this.categoriesRetrieved = false; // Get categories again.
await Promise.all(promises);
this.fetchData();
}
/**
* Load next month.
*/
async loadNext(): Promise<void> {
this.increaseMonth();
this.loaded = false;
try {
await this.fetchEvents();
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.decreaseMonth();
}
this.loaded = true;
}
/**
* Load previous month.
*/
async loadPrevious(): Promise<void> {
this.decreaseMonth();
this.loaded = false;
try {
await this.fetchEvents();
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.increaseMonth();
}
this.loaded = true;
}
/**
* An event was clicked.
*
* @param calendarEvent Calendar event..
* @param event Mouse event.
*/
eventClicked(calendarEvent: AddonCalendarEventToDisplay, event: MouseEvent): void {
this.onEventClicked.emit(calendarEvent.id);
event.stopPropagation();
}
/**
* A day was clicked.
*
* @param day Day.
*/
dayClicked(day: number): void {
this.onDayClicked.emit({ day: day, month: this.month!, year: this.year! });
}
/**
* Check if user is viewing the current month.
*/
calculateIsCurrentMonth(): void {
const now = new Date();
this.currentTime = CoreTimeUtils.instance.timestamp();
this.isCurrentMonth = this.year == now.getFullYear() && this.month == now.getMonth() + 1;
this.isPastMonth = this.year! < now.getFullYear() || (this.year == now.getFullYear() && this.month! < now.getMonth() + 1);
}
/**
* Go to current month.
*/
async goToCurrentMonth(): Promise<void> {
const now = new Date();
const initialMonth = this.month;
const initialYear = this.year;
this.month = now.getMonth() + 1;
this.year = now.getFullYear();
this.loaded = false;
try {
await this.fetchEvents();
this.isCurrentMonth = true;
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.year = initialYear;
this.month = initialMonth;
}
this.loaded = true;
}
/**
* Decrease the current month.
*/
protected decreaseMonth(): void {
if (this.month === 1) {
this.month = 12;
this.year!--;
} else {
this.month!--;
}
}
/**
* Increase the current month.
*/
protected increaseMonth(): void {
if (this.month === 12) {
this.month = 1;
this.year!++;
} else {
this.month!++;
}
}
/**
* Merge online events with the offline events of that period.
*/
protected mergeEvents(): void {
const monthOfflineEvents: { [day: number]: AddonCalendarEventToDisplay[] } =
this.offlineEvents[AddonCalendarHelper.instance.getMonthId(this.year!, this.month!)];
this.weeks.forEach((week) => {
week.days.forEach((day) => {
// Schedule notifications for the events retrieved (only future events will be scheduled).
AddonCalendar.instance.scheduleEventsNotifications(day.eventsFormated!);
if (monthOfflineEvents || this.deletedEvents.length) {
// There is offline data, merge it.
if (this.deletedEvents.length) {
// Mark as deleted the events that were deleted in offline.
day.eventsFormated!.forEach((event) => {
event.deleted = this.deletedEvents.indexOf(event.id) != -1;
});
}
if (this.offlineEditedEventsIds.length) {
// Remove the online events that were modified in offline.
day.events = day.events.filter((event) => this.offlineEditedEventsIds.indexOf(event.id) == -1);
}
if (monthOfflineEvents && monthOfflineEvents[day.mday]) {
// Add the offline events (either new or edited).
day.eventsFormated =
AddonCalendarHelper.instance.sortEvents(day.eventsFormated!.concat(monthOfflineEvents[day.mday]));
}
}
});
});
}
/**
* Undelete a certain event.
*
* @param eventId Event ID.
*/
protected undeleteEvent(eventId: number): void {
if (!this.weeks) {
return;
}
this.weeks.forEach((week) => {
week.days.forEach((day) => {
const event = day.eventsFormated!.find((event) => event.id == eventId);
if (event) {
event.deleted = false;
}
});
});
}
/**
* Returns if the event is in the past or not.
*
* @param event Event object.
* @return True if it's in the past.
*/
protected isEventPast(event: { timestart: number; timeduration: number}): boolean {
return (event.timestart + event.timeduration) < this.currentTime!;
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.undeleteEventObserver?.off();
this.obsDefaultTimeChange?.off();
}
}

View File

@ -0,0 +1,55 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { AddonCalendarCalendarComponent } from './calendar/calendar';
import { AddonCalendarUpcomingEventsComponent } from './upcoming-events/upcoming-events';
import { AddonCalendarFilterPopoverComponent } from './filter/filter';
@NgModule({
declarations: [
AddonCalendarCalendarComponent,
AddonCalendarUpcomingEventsComponent,
AddonCalendarFilterPopoverComponent,
],
imports: [
CommonModule,
IonicModule,
FormsModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
],
providers: [
],
exports: [
AddonCalendarCalendarComponent,
AddonCalendarUpcomingEventsComponent,
AddonCalendarFilterPopoverComponent,
],
entryComponents: [
AddonCalendarFilterPopoverComponent,
],
})
export class AddonCalendarComponentsModule {}

View File

@ -0,0 +1,20 @@
<ion-list>
<ion-radio-group>
<ion-item *ngFor="let type of types" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+type]">
<ion-icon [name]="typeIcons[type]" slot="start"></ion-icon>
<ion-label>{{ 'addon.calendar.' + type + 'events' | translate}}</ion-label>
<ion-toggle [(ngModel)]="filter[type]" (ionChange)="onChange()" slot="end"></ion-toggle>
</ion-item>
<ion-item-divider *ngIf="filter.course || filter.category || filter.group">
<ion-label></ion-label>
</ion-item-divider>
<ion-list *ngIf="filter.course || filter.category || filter.group">
<ion-radio-group [(ngModel)]="courseId" (ionChange)="onChange()">
<ion-item class="ion-text-wrap" *ngFor="let course of courses">
<ion-label><core-format-text [text]="course.fullname"></core-format-text></ion-label>
<ion-radio slot="start" value="{{course.id}}"></ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</ion-radio-group>
</ion-list>

View File

@ -0,0 +1,21 @@
:host {
ion-item {
ion-icon, ion-radio {
margin-right: 8px;
}
> ion-icon {
padding: 4px;
font-size: 20px;
}
}
}
:host-context([dir=rtl]) {
ion-item {
ion-icon, ion-radio {
margin-left: 8px;
margin-right: unset;
}
}
}

View File

@ -0,0 +1,86 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, OnInit } from '@angular/core';
import { CoreEnrolledCourseData } from '@features/courses/services/courses';
import { CoreUtils } from '@services/utils/utils';
import { CoreEvents } from '@singletons/events';
import { AddonCalendarEventType, AddonCalendarProvider } from '../../services/calendar';
import { AddonCalendarFilter, AddonCalendarEventIcons } from '../../services/calendar-helper';
/**
* Component to display the events filter that includes events types and a list of courses.
*/
@Component({
selector: 'addon-calendar-filter-popover',
templateUrl: 'addon-calendar-filter-popover.html',
styleUrls: ['../../calendar-common.scss', 'filter-popover.scss'],
})
export class AddonCalendarFilterPopoverComponent implements OnInit {
@Input() filter: AddonCalendarFilter = {
filtered: false,
courseId: -1,
categoryId: undefined,
course: true,
group: true,
site: true,
user: true,
category: true,
};
courseId = '-1';
@Input() courses: Partial<CoreEnrolledCourseData>[] = [];
typeIcons: AddonCalendarEventIcons[] = [];
types: string[] = [];
constructor() {
CoreUtils.instance.enumKeys(AddonCalendarEventType).forEach((name) => {
const value = AddonCalendarEventType[name];
this.typeIcons[value] = AddonCalendarEventIcons[name];
this.types.push(value);
});
}
/**
* Init the component.
*/
ngOnInit(): void {
this.courseId = this.filter.courseId + '';
}
/**
* Function called when an item is clicked.
*/
onChange(): void {
const courseId = parseInt(this.courseId, 10);
if (courseId > 0) {
const course = this.courses.find((course) => courseId == course.id);
this.filter.courseId = course?.id || -1;
this.filter.categoryId = course?.categoryid;
} else {
this.filter.courseId = -1;
this.filter.categoryId = undefined;
}
this.filter.filtered = this.filter.courseId > 0 || this.types.some((name) => !this.filter[name]);
CoreEvents.trigger<AddonCalendarFilter>(AddonCalendarProvider.FILTER_CHANGED_EVENT, this.filter);
}
}

View File

@ -0,0 +1,29 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="fas-calendar" [message]="'addon.calendar.noevents' | translate">
</core-empty-box>
<ion-list *ngIf="filteredEvents && filteredEvents.length" class="ion-no-margin">
<ng-container *ngFor="let event of filteredEvents">
<ion-item class="ion-text-wrap" [title]="event.name" (click)="eventClicked(event)" class="addon-calendar-event"
[ngClass]="['addon-calendar-eventtype-'+event.eventtype]">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start">
</ion-icon>
<ion-label>
<h2><core-format-text [text]="event.name" [contextLevel]="event.contextLevel"
[contextInstanceId]="event.contextInstanceId"></core-format-text></h2>
<p [innerHTML]="event.formattedtime"></p>
</ion-label>
<ion-note *ngIf="event.offline && !event.deleted" slot="end">
<ion-icon name="far-clock"></ion-icon>
<span class="ion-text-wrap">{{ 'core.notsent' | translate }}</span>
</ion-note>
<ion-note *ngIf="event.deleted" slot="end">
<ion-icon name="fas-trash"></ion-icon>
<span class="ion-text-wrap">{{ 'core.deletedoffline' | translate }}</span>
</ion-note>
</ion-item>
</ng-container>
</ion-list>
</core-loading>

View File

@ -0,0 +1,5 @@
:host {
.addon-calendar-event {
cursor: pointer;
}
}

View File

@ -0,0 +1,324 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnDestroy, OnInit, Input, DoCheck, Output, EventEmitter, KeyValueDiffers, KeyValueDiffer } from '@angular/core';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import {
AddonCalendarProvider,
AddonCalendarEventToDisplay,
AddonCalendar,
AddonCalendarUpdatedEventEvent,
} from '../../services/calendar';
import { AddonCalendarHelper, AddonCalendarFilter } from '../../services/calendar-helper';
import { AddonCalendarOffline } from '../../services/calendar-offline';
import { CoreCategoryData, CoreCourses } from '@features/courses/services/courses';
import { CoreConstants } from '@/core/constants';
import { CoreLocalNotifications } from '@services/local-notifications';
/**
* Component that displays upcoming events.
*/
@Component({
selector: 'addon-calendar-upcoming-events',
templateUrl: 'addon-calendar-upcoming-events.html',
styleUrls: ['../../calendar-common.scss', 'upcoming-events.scss'],
})
export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, OnDestroy {
@Input() filter?: AddonCalendarFilter; // Filter to apply.
@Output() onEventClicked = new EventEmitter<number>();
filteredEvents: AddonCalendarEventToDisplay[] = [];
loaded = false;
protected year?: number;
protected month?: number;
protected categoriesRetrieved = false;
protected categories: { [id: number]: CoreCategoryData } = {};
protected currentSiteId: string;
protected events: AddonCalendarEventToDisplay[] = []; // Events (both online and offline).
protected onlineEvents: AddonCalendarEventToDisplay[] = [];
protected offlineEvents: AddonCalendarEventToDisplay[] = []; // Offline events.
protected deletedEvents: number[] = []; // Events deleted in offline.
protected lookAhead = 0;
protected timeFormat?: string;
protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input.
// Observers.
protected undeleteEventObserver: CoreEventObserver;
protected obsDefaultTimeChange?: CoreEventObserver;
constructor(
differs: KeyValueDiffers,
) {
this.currentSiteId = CoreSites.instance.getCurrentSiteId();
if (CoreLocalNotifications.instance.isAvailable()) { // Re-schedule events if default time changes.
this.obsDefaultTimeChange = CoreEvents.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
AddonCalendar.instance.scheduleEventsNotifications(this.onlineEvents);
}, this.currentSiteId);
}
// Listen for events "undeleted" (offline).
this.undeleteEventObserver = CoreEvents.on(
AddonCalendarProvider.UNDELETED_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (!data || !data.eventId) {
return;
}
// Mark it as undeleted, no need to refresh.
this.undeleteEvent(data.eventId);
// Remove it from the list of deleted events if it's there.
const index = this.deletedEvents.indexOf(data.eventId);
if (index != -1) {
this.deletedEvents.splice(index, 1);
}
},
this.currentSiteId,
);
this.differ = differs.find([]).create();
}
/**
* Component loaded.
*/
ngOnInit(): void {
this.fetchData();
}
/**
* Detect and act upon changes that Angular cant or wont detect on its own (objects and arrays).
*/
ngDoCheck(): void {
// Check if there's any change in the filter object.
const changes = this.differ.diff(this.filter!);
if (changes) {
this.filterEvents();
}
}
/**
* Fetch data.
*
* @return Promise resolved when done.
*/
async fetchData(): Promise<void> {
const promises: Promise<void>[] = [];
promises.push(this.loadCategories());
// Get offline events.
promises.push(AddonCalendarOffline.instance.getAllEditedEvents().then((offlineEvents) => {
// Format data.
const events: AddonCalendarEventToDisplay[] = offlineEvents.map((event) =>
AddonCalendarHelper.instance.formatOfflineEventData(event));
this.offlineEvents = AddonCalendarHelper.instance.sortEvents(events);
return;
}));
// Get events deleted in offline.
promises.push(AddonCalendarOffline.instance.getAllDeletedEventsIds().then((ids) => {
this.deletedEvents = ids;
return;
}));
// Get user preferences.
promises.push(AddonCalendar.instance.getCalendarLookAhead().then((value) => {
this.lookAhead = value;
return;
}));
promises.push(AddonCalendar.instance.getCalendarTimeFormat().then((value) => {
this.timeFormat = value;
return;
}));
try {
await Promise.all(promises);
this.fetchEvents();
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}
this.loaded = true;
}
/**
* Fetch upcoming events.
*
* @return Promise resolved when done.
*/
async fetchEvents(): Promise<void> {
// Don't pass courseId and categoryId, we'll filter them locally.
const result = await AddonCalendar.instance.getUpcomingEvents();
this.onlineEvents = result.events.map((event) => AddonCalendarHelper.instance.formatEventData(event));
// Schedule notifications for the events retrieved.
AddonCalendar.instance.scheduleEventsNotifications(this.onlineEvents);
// Merge the online events with offline data.
this.events = this.mergeEvents();
// Filter events by course.
this.filterEvents();
// Re-calculate the formatted time so it uses the device date.
const promises = this.events.map((event) =>
AddonCalendar.instance.formatEventTime(event, this.timeFormat!).then((time) => {
event.formattedtime = time;
return;
}));
await Promise.all(promises);
}
/**
* Load categories to be able to filter events.
*
* @return Promise resolved when done.
*/
protected async loadCategories(): Promise<void> {
if (this.categoriesRetrieved) {
// Already retrieved, stop.
return;
}
try {
const cats = await CoreCourses.instance.getCategories(0, true);
this.categoriesRetrieved = true;
this.categories = {};
// Index categories by ID.
cats.forEach((category) => {
this.categories[category.id] = category;
});
} catch {
// Ignore errors.
}
}
/**
* Filter events based on the filter popover.
*/
protected filterEvents(): void {
this.filteredEvents = AddonCalendarHelper.instance.getFilteredEvents(this.events, this.filter!, this.categories);
}
/**
* Refresh events.
*
* @param afterChange Whether the refresh is done after an event has changed or has been synced.
* @return Promise resolved when done.
*/
async refreshData(afterChange?: boolean): Promise<void> {
const promises: Promise<void>[] = [];
// Don't invalidate upcoming events after a change, it has already been handled.
if (!afterChange) {
promises.push(AddonCalendar.instance.invalidateAllUpcomingEvents());
}
promises.push(CoreCourses.instance.invalidateCategories(0, true));
promises.push(AddonCalendar.instance.invalidateLookAhead());
promises.push(AddonCalendar.instance.invalidateTimeFormat());
this.categoriesRetrieved = false; // Get categories again.
await Promise.all(promises);
await this.fetchData();
}
/**
* An event was clicked.
*
* @param event Event.
*/
eventClicked(event: AddonCalendarEventToDisplay): void {
this.onEventClicked.emit(event.id);
}
/**
* Merge online events with the offline events of that period.
*
* @return Merged events.
*/
protected mergeEvents(): AddonCalendarEventToDisplay[] {
if (!this.offlineEvents.length && !this.deletedEvents.length) {
// No offline events, nothing to merge.
return this.onlineEvents;
}
const start = Date.now() / 1000;
const end = start + (CoreConstants.SECONDS_DAY * this.lookAhead);
let result: AddonCalendarEventToDisplay[] = this.onlineEvents;
if (this.deletedEvents.length) {
// Mark as deleted the events that were deleted in offline.
result.forEach((event) => {
event.deleted = this.deletedEvents.indexOf(event.id) != -1;
});
}
if (this.offlineEvents.length) {
// Remove the online events that were modified in offline.
result = result.filter((event) => {
const offlineEvent = this.offlineEvents.find((ev) => ev.id == event.id);
return !offlineEvent;
});
}
// Now get the offline events that belong to this period.
const periodOfflineEvents =
this.offlineEvents.filter((event) =>
(event.timestart >= start || event.timestart + event.timeduration >= start) && event.timestart <= end);
// Merge both arrays and sort them.
result = result.concat(periodOfflineEvents);
return AddonCalendarHelper.instance.sortEvents(result);
}
/**
* Undelete a certain event.
*
* @param eventId Event ID.
*/
protected undeleteEvent(eventId: number): void {
const event = this.onlineEvents.find((event) => event.id == eventId);
if (event) {
event.deleted = false;
}
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.undeleteEventObserver?.off();
this.obsDefaultTimeChange?.off();
}
}

View File

@ -0,0 +1,76 @@
{
"allday": "All day",
"calendar": "Calendar",
"calendarevent": "Calendar event",
"calendarevents": "Calendar events",
"calendarreminders": "Calendar reminders",
"categoryevents": "Category events",
"confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?",
"confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?",
"courseevents": "Course events",
"currentmonth": "Current Month",
"daynext": "Next day",
"dayprev": "Previous day",
"defaultnotificationtime": "Default notification time",
"deleteallevents": "Delete all events",
"deleteevent": "Delete event",
"deleteoneevent": "Delete this event",
"durationminutes": "Duration in minutes",
"durationnone": "Without duration",
"durationuntil": "Until",
"editevent": "Editing event",
"errorloadevent": "Error loading event.",
"errorloadevents": "Error loading events.",
"eventcalendareventdeleted": "Calendar event deleted",
"eventduration": "Duration",
"eventendtime": "End time",
"eventkind": "Type of event",
"eventname": "Event title",
"eventstarttime": "Start time",
"eventtype": "Event type",
"fri": "Fri",
"friday": "Friday",
"gotoactivity": "Go to activity",
"groupevents": "Group events",
"invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.",
"invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.",
"mon": "Mon",
"monday": "Monday",
"monthlyview": "Monthly view",
"newevent": "New event",
"noevents": "There are no events",
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event.",
"reminders": "Reminders",
"repeatedevents": "Repeated events",
"repeateditall": "Also apply changes to the other {{$a}} events in this repeat series",
"repeateditthis": "Apply changes to this event only",
"repeatevent": "Repeat this event",
"repeatweeksl": "Repeat weekly, creating altogether",
"sat": "Sat",
"saturday": "Saturday",
"setnewreminder": "Set a new reminder",
"siteevents": "Site events",
"sun": "Sun",
"sunday": "Sunday",
"thu": "Thu",
"thursday": "Thursday",
"today": "Today",
"tomorrow": "Tomorrow",
"tue": "Tue",
"tuesday": "Tuesday",
"typecategory": "Category event",
"typeclose": "Close event",
"typecourse": "Course event",
"typedue": "Due event",
"typegradingdue": "Grading due event",
"typegroup": "Group event",
"typeopen": "Open event",
"typesite": "Site event",
"typeuser": "User event",
"upcomingevents": "Upcoming events",
"userevents": "User events",
"wed": "Wed",
"wednesday": "Wednesday",
"when": "When",
"yesterday": "Yesterday"
}

View File

@ -0,0 +1,92 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>{{ 'addon.calendar.calendarevents' | translate }}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="openFilter($event)" [attr.aria-label]="'core.filter' | translate">
<ion-icon slot="icon-only" name="fas-filter"></ion-icon>
</ion-button>
<core-context-menu>
<core-context-menu-item *ngIf="!isCurrentDay" [priority]="900" [content]="'addon.calendar.today' | translate"
iconAction="fas-calendar-day" (action)="goToCurrentDay()">
</core-context-menu-item>
<core-context-menu-item [hidden]="!loaded || !hasOffline || !isOnline" [priority]="400"
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(undefined, $event)"
[iconAction]="syncIcon" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<!-- Period name and arrows to navigate. -->
<ion-grid class="ion-no-padding safe-area-page">
<ion-row class="ion-align-items-center">
<ion-col class="ion-text-start" *ngIf="currentMoment">
<ion-button fill="clear" (click)="loadPrevious()" [title]="'addon.calendar.dayprev' | translate">
<ion-icon name="fas-chevron-left" slot="icon-only"></ion-icon>
</ion-button>
</ion-col>
<ion-col class="ion-text-center addon-calendar-period">
<h3>{{ periodName }}</h3>
</ion-col>
<ion-col class="ion-text-end" *ngIf="currentMoment">
<ion-button fill="clear" (click)="loadNext()" [title]="'addon.calendar.daynext' | translate">
<ion-icon name="fas-chevron-right" slot="icon-only"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
<core-loading [hideUntil]="loaded" class="safe-area-page">
<!-- There is data to be synchronized --> <!-- There is data to be synchronized -->
<ion-card class="core-warning-card" *ngIf="hasOffline">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>{{ 'core.hasdatatosync' | translate:{$a: 'core.day' | translate} }}</ion-label>
</ion-item>
</ion-card>
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="fas-calendar" inline="true"
[message]="'addon.calendar.noevents' | translate">
</core-empty-box>
<ion-list *ngIf="filteredEvents && filteredEvents.length" class="ion-no-margin">
<ng-container *ngFor="let event of filteredEvents">
<ion-item class="ion-text-wrap" [title]="event.name" (click)="gotoEvent(event.id)"
[class.item-dimmed]="event.ispast" class="addon-calendar-event"
[ngClass]="['addon-calendar-eventtype-'+event.eventtype]">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start">
</ion-icon>
<ion-label>
<h2><core-format-text [text]="event.name" [contextLevel]="event.contextLevel"
[contextInstanceId]="event.contextInstanceId"></core-format-text></h2>
<p [innerHTML]="event.formattedtime"></p>
</ion-label>
<ion-note *ngIf="event.offline && !event.deleted" slot="end">
<ion-icon name="far-clock"></ion-icon>
<span class="ion-text-wrap">{{ 'core.notsent' | translate }}</span>
</ion-note>
<ion-note *ngIf="event.deleted" slot="end">
<ion-icon name="fas-trash"></ion-icon>
<span class="ion-text-wrap">{{ 'core.deletedoffline' | translate }}</span>
</ion-note>
</ion-item>
</ng-container>
</ion-list>
</core-loading>
<!-- Create a calendar event. -->
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canCreate && loaded">
<ion-fab-button (click)="openEdit()" [attr.aria-label]="'addon.calendar.newevent' | translate">
<ion-icon name="fas-plus"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { AddonCalendarComponentsModule } from '../../components/components.module';
import { AddonCalendarDayPage } from './day.page';
const routes: Routes = [
{
path: '',
component: AddonCalendarDayPage,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
AddonCalendarComponentsModule,
],
declarations: [
AddonCalendarDayPage,
],
exports: [RouterModule],
})
export class AddonCalendarDayPageModule {}

View File

@ -0,0 +1,728 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, OnDestroy } from '@angular/core';
import { PopoverController, IonRefresher } from '@ionic/angular';
import { CoreApp } from '@services/app';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreLocalNotifications } from '@services/local-notifications';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTimeUtils } from '@services/utils/time';
import {
AddonCalendarProvider,
AddonCalendar,
AddonCalendarEventToDisplay,
AddonCalendarCalendarDay,
AddonCalendarEventType,
AddonCalendarUpdatedEventEvent,
} from '../../services/calendar';
import { AddonCalendarOffline } from '../../services/calendar-offline';
import { AddonCalendarFilter, AddonCalendarHelper } from '../../services/calendar-helper';
import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync';
import { CoreCategoryData, CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses';
import { CoreCoursesHelper } from '@features/courses/services/courses-helper';
import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter';
import moment from 'moment';
import { Network, NgZone } from '@singletons';
import { CoreNavigator } from '@services/navigator';
import { ActivatedRoute, Params } from '@angular/router';
import { Subscription } from 'rxjs';
import { CoreUtils } from '@services/utils/utils';
/**
* Page that displays the calendar events for a certain day.
*/
@Component({
selector: 'page-addon-calendar-day',
templateUrl: 'day.html',
styleUrls: ['../../calendar-common.scss', 'day.scss'],
})
export class AddonCalendarDayPage implements OnInit, OnDestroy {
protected currentSiteId: string;
protected year!: number;
protected month!: number;
protected day!: number;
protected categories: { [id: number]: CoreCategoryData } = {};
protected events: AddonCalendarEventToDisplay[] = []; // Events (both online and offline).
protected onlineEvents: AddonCalendarEventToDisplay[] = [];
protected offlineEvents: { [monthId: string]: { [day: number]: AddonCalendarEventToDisplay[] } } =
{}; // Offline events classified in month & day.
protected offlineEditedEventsIds: number[] = []; // IDs of events edited in offline.
protected deletedEvents: number[] = []; // Events deleted in offline.
protected timeFormat?: string;
protected currentTime!: number;
// Observers.
protected newEventObserver: CoreEventObserver;
protected discardedObserver: CoreEventObserver;
protected editEventObserver: CoreEventObserver;
protected deleteEventObserver: CoreEventObserver;
protected undeleteEventObserver: CoreEventObserver;
protected syncObserver: CoreEventObserver;
protected manualSyncObserver: CoreEventObserver;
protected onlineObserver: Subscription;
protected obsDefaultTimeChange?: CoreEventObserver;
protected filterChangedObserver: CoreEventObserver;
periodName?: string;
filteredEvents: AddonCalendarEventToDisplay [] = [];
canCreate = false;
courses: Partial<CoreEnrolledCourseData>[] = [];
loaded = false;
hasOffline = false;
isOnline = false;
syncIcon = 'spinner';
isCurrentDay = false;
isPastDay = false;
currentMoment!: moment.Moment;
filter: AddonCalendarFilter = {
filtered: false,
courseId: -1,
categoryId: undefined,
course: true,
group: true,
site: true,
user: true,
category: true,
};
constructor(
protected route: ActivatedRoute,
private popoverCtrl: PopoverController,
) {
this.currentSiteId = CoreSites.instance.getCurrentSiteId();
if (CoreLocalNotifications.instance.isAvailable()) {
// Re-schedule events if default time changes.
this.obsDefaultTimeChange = CoreEvents.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
AddonCalendar.instance.scheduleEventsNotifications(this.onlineEvents);
}, this.currentSiteId);
}
// Listen for events added. When an event is added, reload the data.
this.newEventObserver = CoreEvents.on(
AddonCalendarProvider.NEW_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (data && data.eventId) {
this.loaded = false;
this.refreshData(true, true);
}
},
this.currentSiteId,
);
// Listen for new event discarded event. When it does, reload the data.
this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
this.loaded = false;
this.refreshData(true, true);
}, this.currentSiteId);
// Listen for events edited. When an event is edited, reload the data.
this.editEventObserver = CoreEvents.on(
AddonCalendarProvider.EDIT_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (data && data.eventId) {
this.loaded = false;
this.refreshData(true, true);
}
},
this.currentSiteId,
);
// Refresh data if calendar events are synchronized automatically.
this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => {
this.loaded = false;
this.refreshData(false, true);
}, this.currentSiteId);
// Refresh data if calendar events are synchronized manually but not by this page.
this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data: AddonCalendarSyncEvents) => {
if (data && (data.source != 'day' || data.year != this.year || data.month != this.month || data.day != this.day)) {
this.loaded = false;
this.refreshData(false, true);
}
}, this.currentSiteId);
// Update the events when an event is deleted.
this.deleteEventObserver = CoreEvents.on(
AddonCalendarProvider.DELETED_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (data && !data.sent) {
// Event was deleted in offline. Just mark it as deleted, no need to refresh.
this.hasOffline = this.markAsDeleted(data.eventId, true) || this.hasOffline;
this.deletedEvents.push(data.eventId);
} else {
this.loaded = false;
this.refreshData(false, true);
}
},
this.currentSiteId,
);
// Listen for events "undeleted" (offline).
this.undeleteEventObserver = CoreEvents.on(
AddonCalendarProvider.UNDELETED_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (!data || !data.eventId) {
return;
}
// Mark it as undeleted, no need to refresh.
const found = this.markAsDeleted(data.eventId, false);
// Remove it from the list of deleted events if it's there.
const index = this.deletedEvents.indexOf(data.eventId);
if (index != -1) {
this.deletedEvents.splice(index, 1);
}
if (found) {
// The deleted event belongs to current list. Re-calculate "hasOffline".
this.hasOffline = false;
if (this.events.length != this.onlineEvents.length) {
this.hasOffline = true;
} else {
const event = this.events.find((event) => event.deleted || event.offline);
this.hasOffline = !!event;
}
}
},
this.currentSiteId,
);
this.filterChangedObserver = CoreEvents.on(
AddonCalendarProvider.FILTER_CHANGED_EVENT,
async (data: AddonCalendarFilter) => {
this.filter = data;
// Course viewed has changed, check if the user can create events for this course calendar.
this.canCreate = await AddonCalendarHelper.instance.canEditEvents(this.filter.courseId);
this.filterEvents();
},
);
// Refresh online status when changes.
this.onlineObserver = Network.instance.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.instance.run(() => {
this.isOnline = CoreApp.instance.isOnline();
});
});
}
/**
* View loaded.
*/
ngOnInit(): void {
const types: string[] = [];
CoreUtils.instance.enumKeys(AddonCalendarEventType).forEach((name) => {
const value = AddonCalendarEventType[name];
const filter = this.route.snapshot.queryParams[name];
this.filter[name] = typeof filter == 'undefined' ? true : filter;
types.push(value);
});
this.filter.courseId = parseInt(this.route.snapshot.queryParams['courseId'], 10) || -1;
this.filter.categoryId = parseInt(this.route.snapshot.queryParams['categoryId'], 10) || undefined;
this.filter.filtered = typeof this.filter.courseId != 'undefined' || types.some((name) => !this.filter[name]);
const now = new Date();
this.year = this.route.snapshot.queryParams['year'] || now.getFullYear();
this.month = this.route.snapshot.queryParams['month'] || (now.getMonth() + 1);
this.day = this.route.snapshot.queryParams['day'] || now.getDate();
this.calculateCurrentMoment();
this.calculateIsCurrentDay();
this.fetchData(true);
}
/**
* Fetch all the data required for the view.
*
* @param sync Whether it should try to synchronize offline events.
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
async fetchData(sync?: boolean): Promise<void> {
this.syncIcon = 'spinner';
this.isOnline = CoreApp.instance.isOnline();
if (sync) {
await this.sync();
}
try {
const promises: Promise<void>[] = [];
// Load courses for the popover.
promises.push(CoreCoursesHelper.instance.getCoursesForPopover(this.filter.courseId).then((data) => {
this.courses = data.courses;
return;
}));
// Get categories.
promises.push(this.loadCategories());
// Get offline events.
promises.push(AddonCalendarOffline.instance.getAllEditedEvents().then((offlineEvents) => {
// Classify them by month & day.
this.offlineEvents = AddonCalendarHelper.instance.classifyIntoMonths(offlineEvents);
// Get the IDs of events edited in offline.
this.offlineEditedEventsIds = offlineEvents.filter((event) => event.id! > 0).map((event) => event.id!);
return;
}));
// Get events deleted in offline.
promises.push(AddonCalendarOffline.instance.getAllDeletedEventsIds().then((ids) => {
this.deletedEvents = ids;
return;
}));
// Check if user can create events.
promises.push(AddonCalendarHelper.instance.canEditEvents(this.filter.courseId).then((canEdit) => {
this.canCreate = canEdit;
return;
}));
// Get user preferences.
promises.push(AddonCalendar.instance.getCalendarTimeFormat().then((value) => {
this.timeFormat = value;
return;
}));
await Promise.all(promises);
await this.fetchEvents();
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}
this.loaded = true;
this.syncIcon = 'fas-sync-alt';
}
/**
* Fetch the events for current day.
*
* @return Promise resolved when done.
*/
async fetchEvents(): Promise<void> {
let result: AddonCalendarCalendarDay;
try {
// Don't pass courseId and categoryId, we'll filter them locally.
result = await AddonCalendar.instance.getDayEvents(this.year, this.month, this.day);
this.onlineEvents = result.events.map((event) => AddonCalendarHelper.instance.formatEventData(event));
} catch (error) {
if (CoreApp.instance.isOnline()) {
throw error;
}
// Allow navigating to non-cached days in offline (behave as if using emergency cache).
this.onlineEvents = [];
}
// Calculate the period name. We don't use the one in result because it's in server's language.
this.periodName = CoreTimeUtils.instance.userDate(
new Date(this.year, this.month - 1, this.day).getTime(),
'core.strftimedaydate',
);
// Schedule notifications for the events retrieved (only future events will be scheduled).
AddonCalendar.instance.scheduleEventsNotifications(this.onlineEvents);
// Merge the online events with offline data.
this.events = this.mergeEvents();
// Filter events by course.
this.filterEvents();
this.calculateIsCurrentDay();
// Re-calculate the formatted time so it uses the device date.
const dayTime = this.currentMoment.unix() * 1000;
const promises = this.events.map((event) => {
event.ispast = this.isPastDay || (this.isCurrentDay && this.isEventPast(event));
return AddonCalendar.instance.formatEventTime(event, this.timeFormat!, true, dayTime).then((time) => {
event.formattedtime = time;
return;
});
});
await Promise.all(promises);
}
/**
* Merge online events with the offline events of that period.
*
* @return Merged events.
*/
protected mergeEvents(): AddonCalendarEventToDisplay[] {
this.hasOffline = false;
if (!Object.keys(this.offlineEvents).length && !this.deletedEvents.length) {
// No offline events, nothing to merge.
return this.onlineEvents;
}
const monthOfflineEvents = this.offlineEvents[AddonCalendarHelper.instance.getMonthId(this.year, this.month)];
const dayOfflineEvents = monthOfflineEvents && monthOfflineEvents[this.day];
let result = this.onlineEvents;
if (this.deletedEvents.length) {
// Mark as deleted the events that were deleted in offline.
result.forEach((event) => {
event.deleted = this.deletedEvents.indexOf(event.id) != -1;
if (event.deleted) {
this.hasOffline = true;
}
});
}
if (this.offlineEditedEventsIds.length) {
// Remove the online events that were modified in offline.
result = result.filter((event) => this.offlineEditedEventsIds.indexOf(event.id) == -1);
if (result.length != this.onlineEvents.length) {
this.hasOffline = true;
}
}
if (dayOfflineEvents && dayOfflineEvents.length) {
// Add the offline events (either new or edited).
this.hasOffline = true;
result = AddonCalendarHelper.instance.sortEvents(result.concat(dayOfflineEvents));
}
return result;
}
/**
* Filter events based on the filter popover.
*/
protected filterEvents(): void {
this.filteredEvents = AddonCalendarHelper.instance.getFilteredEvents(this.events, this.filter, this.categories);
}
/**
* Refresh the data.
*
* @param refresher Refresher.
* @param done Function to call when done.
* @return Promise resolved when done.
*/
async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void): Promise<void> {
if (!this.loaded) {
return;
}
await this.refreshData(true).finally(() => {
refresher?.detail.complete();
done && done();
});
}
/**
* Refresh the data.
*
* @param sync Whether it should try to synchronize offline events.
* @param afterChange Whether the refresh is done after an event has changed or has been synced.
* @return Promise resolved when done.
*/
async refreshData(sync?: boolean, afterChange?: boolean): Promise<void> {
this.syncIcon = 'spinner';
const promises: Promise<void>[] = [];
// Don't invalidate day events after a change, it has already been handled.
if (!afterChange) {
promises.push(AddonCalendar.instance.invalidateDayEvents(this.year, this.month, this.day));
}
promises.push(AddonCalendar.instance.invalidateAllowedEventTypes());
promises.push(CoreCourses.instance.invalidateCategories(0, true));
promises.push(AddonCalendar.instance.invalidateTimeFormat());
await Promise.all(promises).finally(() =>
this.fetchData(sync));
}
/**
* Load categories to be able to filter events.
*
* @return Promise resolved when done.
*/
protected async loadCategories(): Promise<void> {
try {
const cats = await CoreCourses.instance.getCategories(0, true);
this.categories = {};
// Index categories by ID.
cats.forEach((category) => {
this.categories[category.id] = category;
});
} catch {
// Ignore errors.
}
}
/**
* Try to synchronize offline events.
*
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
protected async sync(showErrors?: boolean): Promise<void> {
try {
const result = await AddonCalendarSync.instance.syncEvents();
if (result.warnings && result.warnings.length) {
CoreDomUtils.instance.showErrorModal(result.warnings[0]);
}
if (result.updated) {
// Trigger a manual sync event.
result.source = 'day';
result.day = this.day;
result.month = this.month;
result.year = this.year;
CoreEvents.trigger<AddonCalendarSyncEvents>(AddonCalendarSyncProvider.MANUAL_SYNCED, result, this.currentSiteId);
}
} catch (error) {
if (showErrors) {
CoreDomUtils.instance.showErrorModalDefault(error, 'core.errorsync', true);
}
}
}
/**
* Navigate to a particular event.
*
* @param eventId Event to load.
*/
gotoEvent(eventId: number): void {
if (eventId < 0) {
// It's an offline event, go to the edit page.
this.openEdit(eventId);
} else {
CoreNavigator.instance.navigateToSitePath('/calendar/event', { params: { id: eventId } });
}
}
/**
* Show the context menu.
*
* @param event Event.
*/
async openFilter(event: MouseEvent): Promise<void> {
const popover = await this.popoverCtrl.create({
component: AddonCalendarFilterPopoverComponent,
componentProps: {
courses: this.courses,
filter: this.filter,
},
event,
});
await popover.present();
}
/**
* Open page to create/edit an event.
*
* @param eventId Event ID to edit.
*/
openEdit(eventId?: number): void {
const params: Params = {};
if (eventId) {
params.eventId = eventId;
} else {
// It's a new event, set the time.
params.timestamp = moment().year(this.year).month(this.month - 1).date(this.day).unix() * 1000;
}
if (this.filter.courseId) {
params.courseId = this.filter.courseId;
}
CoreNavigator.instance.navigateToSitePath('/calendar/edit', { params });
}
/**
* Calculate current moment.
*/
calculateCurrentMoment(): void {
this.currentMoment = moment().year(this.year).month(this.month - 1).date(this.day);
}
/**
* Check if user is viewing the current day.
*/
calculateIsCurrentDay(): void {
const now = new Date();
this.currentTime = CoreTimeUtils.instance.timestamp();
this.isCurrentDay = this.year == now.getFullYear() && this.month == now.getMonth() + 1 && this.day == now.getDate();
this.isPastDay = this.year < now.getFullYear() || (this.year == now.getFullYear() && this.month < now.getMonth()) ||
(this.year == now.getFullYear() && this.month == now.getMonth() + 1 && this.day < now.getDate());
}
/**
* Go to current day.
*/
async goToCurrentDay(): Promise<void> {
const now = new Date();
const initialDay = this.day;
const initialMonth = this.month;
const initialYear = this.year;
this.day = now.getDate();
this.month = now.getMonth() + 1;
this.year = now.getFullYear();
this.calculateCurrentMoment();
this.loaded = false;
try {
await this.fetchEvents();
this.isCurrentDay = true;
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.year = initialYear;
this.month = initialMonth;
this.day = initialDay;
this.calculateCurrentMoment();
}
this.loaded = true;
}
/**
* Load next day.
*/
async loadNext(): Promise<void> {
this.increaseDay();
this.loaded = false;
try {
await this.fetchEvents();
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.decreaseDay();
}
this.loaded = true;
}
/**
* Load previous day.
*/
async loadPrevious(): Promise<void> {
this.decreaseDay();
this.loaded = false;
try {
await this.fetchEvents();
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.increaseDay();
}
this.loaded = true;
}
/**
* Decrease the current day.
*/
protected decreaseDay(): void {
this.currentMoment.subtract(1, 'day');
this.year = this.currentMoment.year();
this.month = this.currentMoment.month() + 1;
this.day = this.currentMoment.date();
}
/**
* Increase the current day.
*/
protected increaseDay(): void {
this.currentMoment.add(1, 'day');
this.year = this.currentMoment.year();
this.month = this.currentMoment.month() + 1;
this.day = this.currentMoment.date();
}
/**
* Find an event and mark it as deleted.
*
* @param eventId Event ID.
* @param deleted Whether to mark it as deleted or not.
* @return Whether the event was found.
*/
protected markAsDeleted(eventId: number, deleted: boolean): boolean {
const event = this.onlineEvents.find((event) => event.id == eventId);
if (event) {
event.deleted = deleted;
return true;
}
return false;
}
/**
* Returns if the event is in the past or not.
*
* @param event Event object.
* @return True if it's in the past.
*/
isEventPast(event: AddonCalendarEventToDisplay): boolean {
return (event.timestart + event.timeduration) < this.currentTime;
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.newEventObserver?.off();
this.discardedObserver?.off();
this.editEventObserver?.off();
this.deleteEventObserver?.off();
this.undeleteEventObserver?.off();
this.syncObserver?.off();
this.manualSyncObserver?.off();
this.onlineObserver?.unsubscribe();
this.filterChangedObserver?.off();
this.obsDefaultTimeChange?.off();
}
}

View File

@ -0,0 +1,9 @@
:host {
.addon-calendar-period {
flex-grow: 3;
h3 {
margin-top: 10px;
font-size: 1.2rem;
}
}
}

View File

@ -0,0 +1,232 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>{{ title | translate }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="loaded">
<form [formGroup]="form" *ngIf="!error" #editEventForm>
<!-- Event name. -->
<ion-item class="ion-text-wrap">
<ion-label position="stacked"><h2 [core-mark-required]="true">
{{ 'addon.calendar.eventname' | translate }}
</h2>
</ion-label>
<ion-input type="text" name="name" [placeholder]="'addon.calendar.eventname' | translate" formControlName="name">
</ion-input>
<core-input-errors item-content [control]="form.controls.name" [errorMessages]="errors"></core-input-errors>
</ion-item>
<!-- Date. -->
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<h2 [core-mark-required]="true">
{{ 'core.date' | translate }}
</h2>
</ion-label>
<ion-datetime formControlName="timestart" [placeholder]="'core.date' | translate" [displayFormat]="dateFormat">
</ion-datetime>
<core-input-errors item-content [control]="form.controls.timestart" [errorMessages]="errors"></core-input-errors>
</ion-item>
<!-- Type. -->
<ion-item class="ion-text-wrap addon-calendar-eventtype-container">
<ion-label id="addon-calendar-eventtype-label">
<h2 [core-mark-required]="true">
{{ 'addon.calendar.eventkind' | translate }}
</h2>
</ion-label>
<ion-select formControlName="eventtype" aria-labelledby="addon-calendar-eventtype-label" interface="action-sheet"
[disabled]="eventTypes.length == 1">
<ion-select-option *ngFor="let type of eventTypes" [value]="type.value">
{{ type.name | translate }}
</ion-select-option>
</ion-select>
</ion-item>
<!-- Category. -->
<ion-item class="ion-text-wrap" *ngIf="typeControl.value == 'category'">
<ion-label id="addon-calendar-category-label">
<h2 [core-mark-required]="true">
{{ 'core.category' | translate }}
</h2>
</ion-label>
<ion-select formControlName="categoryid" aria-labelledby="addon-calendar-category-label" interface="action-sheet"
[placeholder]="'core.noselection' | translate">
<ion-select-option *ngFor="let category of categories" [value]="category.id">
{{ category.name }}
</ion-select-option>
</ion-select>
</ion-item>
<!-- Course. -->
<ion-item class="ion-text-wrap" *ngIf="typeControl.value == 'course'">
<ion-label id="addon-calendar-course-label">
<h2 [core-mark-required]="true">
{{ 'core.course' | translate }}
</h2>
</ion-label>
<ion-select formControlName="courseid" aria-labelledby="addon-calendar-course-label" interface="action-sheet"
[placeholder]="'core.noselection' | translate">
<ion-select-option *ngFor="let course of courses" [value]="course.id">{{ course.fullname }}</ion-select-option>
</ion-select>
</ion-item>
<!-- Group. -->
<ng-container *ngIf="typeControl.value == 'group'">
<!-- Select the course. -->
<ion-item class="ion-text-wrap">
<ion-label id="addon-calendar-groupcourse-label">
<h2 [core-mark-required]="true">
{{ 'core.course' | translate }}
</h2>
</ion-label>
<ion-select formControlName="groupcourseid" aria-labelledby="addon-calendar-groupcourse-label"
interface="action-sheet" [placeholder]="'core.noselection' | translate"
(ionChange)="groupCourseSelected($event)">
<ion-select-option *ngFor="let course of courses" [value]="course.id">
{{ course.fullname }}
</ion-select-option>
</ion-select>
</ion-item>
<!-- The course has no groups. -->
<ion-item class="ion-text-wrap" *ngIf="!loadingGroups && courseGroupSet && !groups.length" class="core-danger-item">
<ion-label><p>{{ 'core.coursenogroups' | translate }}</p></ion-label>
</ion-item>
<!-- Select the group. -->
<ion-item class="ion-text-wrap" *ngIf="!loadingGroups && groups.length > 0">
<ion-label id="addon-calendar-group-label">
<h2 [core-mark-required]="true">
{{ 'core.group' | translate }}
</h2>
</ion-label>
<ion-select formControlName="groupid" aria-labelledby="addon-calendar-group-label" interface="action-sheet"
[placeholder]="'core.noselection' | translate">
<ion-select-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-select-option>
</ion-select>
</ion-item>
<!-- Loading groups. -->
<ion-item class="ion-text-wrap" *ngIf="loadingGroups">
<ion-label><ion-spinner *ngIf="loadingGroups"></ion-spinner></ion-label>
</ion-item>
</ng-container>
<!-- Advanced options. -->
<ion-item-divider class="ion-text-wrap" (click)="toggleAdvanced()" class="core-expandable">
<ion-icon *ngIf="!advanced" name="fas-caret-right" slot="start"></ion-icon>
<ion-icon *ngIf="advanced" name="fas-caret-down" slot="start"></ion-icon>
<ion-label>
<span *ngIf="!advanced">{{ 'core.showmore' | translate }}</span>
<span *ngIf="advanced">{{ 'core.showless' | translate }}</span>
</ion-label>
</ion-item-divider>
<div [hidden]="!advanced">
<!-- Description. -->
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<h2>{{ 'core.description' | translate }}</h2>
</ion-label>
<core-rich-text-editor item-content [control]="descriptionControl"
[placeholder]="'core.description' | translate" name="description" [component]="component"
[componentId]="eventId" [autoSave]="false"></core-rich-text-editor>
</ion-item>
<!-- Location. -->
<ion-item class="ion-text-wrap">
<ion-label position="stacked"><h2>{{ 'core.location' | translate }}</h2></ion-label>
<ion-input type="text" name="location" [placeholder]="'core.location' | translate" formControlName="location">
</ion-input>
</ion-item>
<!-- Duration. -->
<div class="ion-text-wrap" class="addon-calendar-radio-container">
<ion-radio-group formControlName="duration">
<ion-item class="addon-calendar-radio-title">
<ion-label>
<h2>
{{ 'addon.calendar.eventduration' | translate }}
</h2>
</ion-label>
</ion-item>
<ion-item>
<ion-radio slot="start" value="0"></ion-radio>
<ion-label>{{ 'addon.calendar.durationnone' | translate }}</ion-label>
</ion-item>
<ion-item (click)="selectDuration('1')">
<ion-radio slot="start" value="1"></ion-radio>
<ion-label>{{ 'addon.calendar.durationuntil' | translate }}</ion-label>
<ion-datetime formControlName="timedurationuntil"
[placeholder]="'addon.calendar.durationuntil' | translate"
[displayFormat]="dateFormat" [disabled]="form.controls.duration.value != 1"></ion-datetime>
</ion-item>
<ion-item (click)="selectDuration('2')">
<ion-radio slot="start" value="2"></ion-radio>
<ion-label>{{ 'addon.calendar.durationminutes' | translate }}</ion-label>
<ion-input type="number" name="timedurationminutes" slot="end"
[placeholder]="'addon.calendar.durationminutes' | translate"
formControlName="timedurationminutes" [disabled]="form.controls.duration.value != 2"></ion-input>
</ion-item>
</ion-radio-group>
</div>
<!-- Repeat (for new events). -->
<ng-container *ngIf="!eventId || eventId < 0">
<ion-item class="ion-text-wrap">
<ion-label>
<h2>{{ 'addon.calendar.repeatevent' | translate }}</h2>
</ion-label>
<ion-checkbox slot="end" formControlName="repeat"></ion-checkbox>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="form.controls.repeat.value">
<ion-label position="stacked"><h2>{{ 'addon.calendar.repeatweeksl' | translate }}</h2></ion-label>
<ion-input type="number" name="repeats" formControlName="repeats"></ion-input>
</ion-item>
</ng-container>
<!-- Apply to all events or just this one (editing repeated events). -->
<div *ngIf="eventRepeatId" class="ion-text-wrap" class="addon-calendar-radio-container">
<ion-radio-group formControlName="repeateditall">
<ion-item class="addon-calendar-radio-title">
<ion-label>
<h2>
{{ 'addon.calendar.repeatedevents' | translate }}
</h2>
</ion-label>
</ion-item>
<ion-item>
<ion-label>{{ 'addon.calendar.repeateditall' | translate:{$a: otherEventsCount} }}</ion-label>
<ion-radio slot="start" [value]="1"></ion-radio>
</ion-item>
<ion-item>
<ion-label>{{ 'addon.calendar.repeateditthis' | translate }}</ion-label>
<ion-radio slot="start" [value]="0"></ion-radio>
</ion-item>
</ion-radio-group>
</div>
</div>
<ion-item>
<ion-label>
<ion-row>
<ion-col>
<ion-button expand="block" (click)="submit()" [disabled]="!form.valid">
{{ 'core.save' | translate }}
</ion-button>
</ion-col>
<ion-col *ngIf="hasOffline && eventId && eventId < 0">
<ion-button expand="block" color="light" (click)="discard()">{{ 'core.discard' | translate }}</ion-button>
</ion-col>
</ion-row>
</ion-label>
</ion-item>
</form>
</core-loading>
</ion-content>

View File

@ -0,0 +1,52 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreEditorComponentsModule } from '@features/editor/components/components.module';
import { AddonCalendarEditEventPage } from './edit-event.page';
const routes: Routes = [
{
path: '',
component: AddonCalendarEditEventPage,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CommonModule,
IonicModule,
FormsModule,
ReactiveFormsModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CoreEditorComponentsModule,
],
declarations: [
AddonCalendarEditEventPage,
],
exports: [RouterModule],
})
export class AddonCalendarEditEventPageModule {}

View File

@ -0,0 +1,636 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { IonRefresher, NavController } from '@ionic/angular';
import { CoreEvents } from '@singletons/events';
import { CoreGroup, CoreGroups } from '@services/groups';
import { CoreSites } from '@services/sites';
import { CoreSync } from '@services/sync';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { CoreCategoryData, CoreCourses, CoreCourseSearchedData, CoreEnrolledCourseData } from '@features/courses/services/courses';
// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreEditorRichTextEditorComponent } from '@features/editor/components/rich-text-editor/rich-text-editor.ts';
import {
AddonCalendarProvider,
AddonCalendarGetCalendarAccessInformationWSResponse,
AddonCalendarEvent,
AddonCalendarEventType,
AddonCalendar,
AddonCalendarSubmitCreateUpdateFormDataWSParams,
AddonCalendarUpdatedEventEvent,
} from '../../services/calendar';
import { AddonCalendarOffline } from '../../services/calendar-offline';
import { AddonCalendarEventTypeOption, AddonCalendarHelper } from '../../services/calendar-helper';
import { AddonCalendarSync, AddonCalendarSyncProvider } from '../../services/calendar-sync';
import { CoreSite } from '@classes/site';
import { Translate } from '@singletons';
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
import { ActivatedRoute } from '@angular/router';
import { AddonCalendarOfflineEventDBRecord } from '../../services/database/calendar-offline';
import { CoreError } from '@classes/errors/error';
/**
* Page that displays a form to create/edit an event.
*/
@Component({
selector: 'page-addon-calendar-edit-event',
templateUrl: 'edit-event.html',
styleUrls: ['edit-event.scss'],
})
export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
@ViewChild(CoreEditorRichTextEditorComponent) descriptionEditor!: CoreEditorRichTextEditorComponent;
@ViewChild('editEventForm') formElement!: ElementRef;
title = 'addon.calendar.newevent';
dateFormat: string;
component = AddonCalendarProvider.COMPONENT;
loaded = false;
hasOffline = false;
eventTypes: AddonCalendarEventTypeOption[] = [];
categories: CoreCategoryData[] = [];
courses: CoreCourseSearchedData[] | CoreEnrolledCourseData[] = [];
groups: CoreGroup[] = [];
loadingGroups = false;
courseGroupSet = false;
advanced = false;
errors: Record<string, string>;
error = false;
eventRepeatId?: number;
otherEventsCount = 0;
eventId?: number;
// Form variables.
form: FormGroup;
typeControl: FormControl;
groupControl: FormControl;
descriptionControl: FormControl;
protected courseId!: number;
protected originalData?: AddonCalendarOfflineEventDBRecord;
protected currentSite: CoreSite;
protected types: { [name: string]: boolean } = {}; // Object with the supported types.
protected showAll = false;
protected isDestroyed = false;
protected gotEventData = false;
constructor(
protected navCtrl: NavController,
protected route: ActivatedRoute,
protected fb: FormBuilder,
) {
this.currentSite = CoreSites.instance.getCurrentSite()!;
this.errors = {
required: Translate.instance.instant('core.required'),
};
// Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them.
this.dateFormat = CoreTimeUtils.instance.convertPHPToMoment(Translate.instance.instant('core.strftimedatetimeshort'))
.replace(/[[\]]/g, '');
this.form = new FormGroup({});
// Initialize form variables.
this.typeControl = this.fb.control('', Validators.required);
this.groupControl = this.fb.control('');
this.descriptionControl = this.fb.control('');
this.form.addControl('name', this.fb.control('', Validators.required));
this.form.addControl('eventtype', this.typeControl);
this.form.addControl('categoryid', this.fb.control(''));
this.form.addControl('groupcourseid', this.fb.control(''));
this.form.addControl('groupid', this.groupControl);
this.form.addControl('description', this.descriptionControl);
this.form.addControl('location', this.fb.control(''));
this.form.addControl('duration', this.fb.control(0));
this.form.addControl('timedurationminutes', this.fb.control(''));
this.form.addControl('repeat', this.fb.control(false));
this.form.addControl('repeats', this.fb.control('1'));
this.form.addControl('repeateditall', this.fb.control(1));
}
/**
* Component being initialized.
*/
ngOnInit(): void {
this.eventId = this.route.snapshot.queryParams['eventId'];
this.courseId = parseInt(this.route.snapshot.queryParams['courseId'], 10) || 0;
this.title = this.eventId ? 'addon.calendar.editevent' : 'addon.calendar.newevent';
const timestamp = parseInt(this.route.snapshot.queryParams['timestamp'], 10);
const currentDate = CoreTimeUtils.instance.toDatetimeFormat(timestamp);
this.form.addControl('timestart', this.fb.control(currentDate, Validators.required));
this.form.addControl('timedurationuntil', this.fb.control(currentDate));
this.form.addControl('courseid', this.fb.control(this.courseId));
this.fetchData().finally(() => {
this.originalData = CoreUtils.instance.clone(this.form.value);
this.loaded = true;
});
}
/**
* Fetch the data needed to render the form.
*
* @param refresh Whether it's refreshing data.
* @return Promise resolved when done.
*/
protected async fetchData(): Promise<void> {
let accessInfo: AddonCalendarGetCalendarAccessInformationWSResponse;
this.error = false;
// Get access info.
try {
accessInfo = await AddonCalendar.instance.getAccessInformation(this.courseId);
this.types = await AddonCalendar.instance.getAllowedEventTypes(this.courseId);
const promises: Promise<void>[] = [];
const eventTypes = AddonCalendarHelper.instance.getEventTypeOptions(this.types);
if (!eventTypes.length) {
throw new CoreError(Translate.instance.instant('addon.calendar.nopermissiontoupdatecalendar'));
}
if (this.eventId && !this.gotEventData) {
// Editing an event, get the event data. Wait for sync first.
promises.push(AddonCalendarSync.instance.waitForSync(AddonCalendarSyncProvider.SYNC_ID).then(async () => {
// Do not block if the scope is already destroyed.
if (!this.isDestroyed && this.eventId) {
CoreSync.instance.blockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
}
let eventForm: AddonCalendarEvent | AddonCalendarOfflineEventDBRecord | undefined;
// Get the event offline data if there's any.
try {
eventForm = await AddonCalendarOffline.instance.getEvent(this.eventId!);
this.hasOffline = true;
} catch {
// No offline data.
this.hasOffline = false;
}
if (this.eventId! > 0) {
// It's an online event. get its data from server.
const event = await AddonCalendar.instance.getEventById(this.eventId!);
if (!eventForm) {
eventForm = event; // Use offline data first.
}
this.eventRepeatId = event?.repeatid;
if (this.eventRepeatId) {
this.otherEventsCount = event.eventcount ? event.eventcount - 1 : 0;
}
}
this.gotEventData = true;
if (eventForm) {
// Load the data in the form.
return this.loadEventData(eventForm, this.hasOffline);
}
return;
}));
}
if (this.types.category) {
// Get the categories.
promises.push(this.fetchCategories());
}
this.showAll = CoreUtils.instance.isTrueOrOne(this.currentSite.getStoredConfig('calendar_adminseesall')) &&
accessInfo.canmanageentries;
if (this.types.course || this.types.groups) {
promises.push(this.fetchCourses());
}
await Promise.all(promises);
if (!this.typeControl.value) {
// Initialize event type value. If course is allowed, select it first.
if (this.types.course) {
this.typeControl.setValue(AddonCalendarEventType.COURSE);
} else {
this.typeControl.setValue(eventTypes[0].value);
}
}
this.eventTypes = eventTypes;
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'Error getting data.');
this.error = true;
}
}
protected async fetchCategories(): Promise<void> {
this.categories = await CoreCourses.instance.getCategories(0, true);
}
protected async fetchCourses(): Promise<void> {
// Get the courses.
let courses = await (this.showAll ? CoreCourses.instance.getCoursesByField() : CoreCourses.instance.getUserCourses());
if (courses.length < 0) {
this.courses = [];
return;
}
const courseFillterFullname = (course: CoreCourseSearchedData | CoreEnrolledCourseData): Promise<void> =>
CoreFilterHelper.instance.getFiltersAndFormatText(course.fullname, 'course', course.id)
.then((result) => {
course.fullname = result.text;
return;
}).catch(() => {
// Ignore errors.
});
if (this.showAll) {
// Remove site home from the list of courses.
const siteHomeId = CoreSites.instance.getCurrentSiteHomeId();
if ('contacts' in courses[0]) {
courses = (courses as CoreCourseSearchedData[]).filter((course) => course.id != siteHomeId);
} else {
courses = (courses as CoreEnrolledCourseData[]).filter((course) => course.id != siteHomeId);
}
}
// Format the name of the courses.
if ('contacts' in courses[0]) {
await Promise.all((courses as CoreCourseSearchedData[]).map(courseFillterFullname));
} else {
await Promise.all((courses as CoreEnrolledCourseData[]).map(courseFillterFullname));
}
// Sort courses by name.
this.courses = courses.sort((a, b) => {
const compareA = a.fullname.toLowerCase();
const compareB = b.fullname.toLowerCase();
return compareA.localeCompare(compareB);
});
}
/**
* Load an event data into the form.
*
* @param event Event data.
* @param isOffline Whether the data is from offline or not.
* @return Promise resolved when done.
*/
protected async loadEventData(
event: AddonCalendarEvent | AddonCalendarOfflineEventDBRecord,
isOffline: boolean,
): Promise<void> {
if (!event) {
return;
}
const offlineEvent = (event as AddonCalendarOfflineEventDBRecord);
const onlineEvent = (event as AddonCalendarEvent);
const courseId = isOffline ? offlineEvent.courseid : onlineEvent.course?.id;
this.form.controls.name.setValue(event.name);
this.form.controls.timestart.setValue(CoreTimeUtils.instance.toDatetimeFormat(event.timestart * 1000));
this.form.controls.eventtype.setValue(event.eventtype);
this.form.controls.categoryid.setValue(event.categoryid || '');
this.form.controls.courseid.setValue(courseId || '');
this.form.controls.groupcourseid.setValue(courseId || '');
this.form.controls.groupid.setValue(event.groupid || '');
this.form.controls.description.setValue(event.description);
this.form.controls.location.setValue(event.location);
if (isOffline) {
// It's an offline event, use the data as it is.
this.form.controls.duration.setValue(offlineEvent.duration);
this.form.controls.timedurationuntil.setValue(
CoreTimeUtils.instance.toDatetimeFormat(((offlineEvent.timedurationuntil || 0) * 1000) || Date.now()),
);
this.form.controls.timedurationminutes.setValue(offlineEvent.timedurationminutes || '');
this.form.controls.repeat.setValue(!!offlineEvent.repeat);
this.form.controls.repeats.setValue(offlineEvent.repeats || '1');
this.form.controls.repeateditall.setValue(offlineEvent.repeateditall || 1);
} else {
// Online event, we'll have to calculate the data.
if (onlineEvent.timeduration > 0) {
this.form.controls.duration.setValue(1);
this.form.controls.timedurationuntil.setValue(CoreTimeUtils.instance.toDatetimeFormat(
(onlineEvent.timestart + onlineEvent.timeduration) * 1000,
));
} else {
// No duration.
this.form.controls.duration.setValue(0);
this.form.controls.timedurationuntil.setValue(CoreTimeUtils.instance.toDatetimeFormat());
}
this.form.controls.timedurationminutes.setValue('');
this.form.controls.repeat.setValue(!!onlineEvent.repeatid);
this.form.controls.repeats.setValue(onlineEvent.eventcount || '1');
this.form.controls.repeateditall.setValue(1);
}
if (event.eventtype == AddonCalendarEventType.GROUP && courseId) {
await this.loadGroups(courseId);
}
}
/**
* Pull to refresh.
*
* @param refresher Refresher.
*/
refreshData(refresher?: CustomEvent<IonRefresher>): void {
const promises = [
AddonCalendar.instance.invalidateAccessInformation(this.courseId),
AddonCalendar.instance.invalidateAllowedEventTypes(this.courseId),
];
if (this.types) {
if (this.types.category) {
promises.push(CoreCourses.instance.invalidateCategories(0, true));
}
if (this.types.course || this.types.groups) {
if (this.showAll) {
promises.push(CoreCourses.instance.invalidateCoursesByField());
} else {
promises.push(CoreCourses.instance.invalidateUserCourses());
}
}
}
Promise.all(promises).finally(() => {
this.fetchData().finally(() => {
refresher?.detail.complete();
});
});
}
/**
* A course was selected, get its groups.
*
* @param courseId Course ID.
*/
async groupCourseSelected(courseId: number): Promise<void> {
if (!courseId) {
return;
}
const modal = await CoreDomUtils.instance.showModalLoading();
try {
await this.loadGroups(courseId);
this.groupControl.setValue('');
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'Error getting data.');
}
modal.dismiss();
}
/**
* Load groups of a certain course.
*
* @param courseId Course ID.
* @return Promise resolved when done.
*/
protected async loadGroups(courseId: number): Promise<void> {
this.loadingGroups = true;
try {
this.groups = await CoreGroups.instance.getUserGroupsInCourse(courseId);
this.courseGroupSet = true;
} finally {
this.loadingGroups = false;
}
}
/**
* Show or hide advanced form fields.
*/
toggleAdvanced(): void {
this.advanced = !this.advanced;
}
selectDuration(duration: string): void {
this.form.controls.duration.setValue(duration);
}
/**
* Create the event.
*/
async submit(): Promise<void> {
// Validate data.
const formData = this.form.value;
const timeStartDate = CoreTimeUtils.instance.convertToTimestamp(formData.timestart);
const timeUntilDate = CoreTimeUtils.instance.convertToTimestamp(formData.timedurationuntil);
const timeDurationMinutes = parseInt(formData.timedurationminutes || '', 10);
let error: string | undefined;
if (formData.eventtype == AddonCalendarEventType.COURSE && !formData.courseid) {
error = 'core.selectacourse';
} else if (formData.eventtype == AddonCalendarEventType.GROUP && !formData.groupcourseid) {
error = 'core.selectacourse';
} else if (formData.eventtype == AddonCalendarEventType.GROUP && !formData.groupid) {
error = 'core.selectagroup';
} else if (formData.eventtype == AddonCalendarEventType.CATEGORY && !formData.categoryid) {
error = 'core.selectacategory';
} else if (formData.duration == 1 && timeStartDate > timeUntilDate) {
error = 'addon.calendar.invalidtimedurationuntil';
} else if (formData.duration == 2 && (isNaN(timeDurationMinutes) || timeDurationMinutes < 1)) {
error = 'addon.calendar.invalidtimedurationminutes';
}
if (error) {
// Show error and stop.
CoreDomUtils.instance.showErrorModal(Translate.instance.instant(error));
return;
}
// Format the data to send.
const data: AddonCalendarSubmitCreateUpdateFormDataWSParams = {
name: formData.name,
eventtype: formData.eventtype,
timestart: timeStartDate,
description: {
text: formData.description || '',
format: 1,
},
location: formData.location,
duration: formData.duration,
repeat: formData.repeat,
};
if (formData.eventtype == AddonCalendarEventType.COURSE) {
data.courseid = formData.courseid;
} else if (formData.eventtype == AddonCalendarEventType.GROUP) {
data.groupcourseid = formData.groupcourseid;
data.groupid = formData.groupid;
} else if (formData.eventtype == AddonCalendarEventType.CATEGORY) {
data.categoryid = formData.categoryid;
}
if (formData.duration == 1) {
data.timedurationuntil = timeUntilDate;
} else if (formData.duration == 2) {
data.timedurationminutes = formData.timedurationminutes;
}
if (formData.repeat) {
data.repeats = Number(formData.repeats);
}
if (this.eventRepeatId) {
data.repeatid = this.eventRepeatId;
data.repeateditall = formData.repeateditall;
}
// Send the data.
const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
let event: AddonCalendarEvent | AddonCalendarOfflineEventDBRecord;
try {
const result = await AddonCalendar.instance.submitEvent(this.eventId, data);
event = result.event;
CoreDomUtils.instance.triggerFormSubmittedEvent(this.formElement, result.sent, this.currentSite.getId());
if (result.sent) {
// Event created or edited, invalidate right days & months.
const numberOfRepetitions = formData.repeat ? formData.repeats :
(data.repeateditall && this.otherEventsCount ? this.otherEventsCount + 1 : 1);
try {
await AddonCalendarHelper.instance.refreshAfterChangeEvent(result.event, numberOfRepetitions);
} catch {
// Ignore errors.
}
}
this.returnToList(event);
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'Error sending data.');
}
modal.dismiss();
}
/**
* Convenience function to update or return to event list depending on device.
*
* @param event Event.
*/
protected returnToList(event?: AddonCalendarEvent | AddonCalendarOfflineEventDBRecord): void {
// Unblock the sync because the view will be destroyed and the sync process could be triggered before ngOnDestroy.
this.unblockSync();
if (this.eventId && this.eventId > 0) {
// Editing an event.
CoreEvents.trigger<AddonCalendarUpdatedEventEvent>(
AddonCalendarProvider.EDIT_EVENT_EVENT,
{ eventId: this.eventId },
this.currentSite.getId(),
);
} else {
if (event) {
CoreEvents.trigger<AddonCalendarUpdatedEventEvent>(
AddonCalendarProvider.NEW_EVENT_EVENT,
{ eventId: event.id! },
this.currentSite.getId(),
);
} else {
CoreEvents.trigger(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, {}, this.currentSite.getId());
}
}
/* if (this.svComponent && this.svComponent.isOn()) {
// Empty form.
this.hasOffline = false;
this.form.reset(this.originalData);
this.originalData = CoreUtils.instance.clone(this.form.value);
} else {*/
this.originalData = undefined; // Avoid asking for confirmation.
this.navCtrl.pop();
}
/**
* Discard an offline saved discussion.
*/
async discard(): Promise<void> {
try {
await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.areyousure'));
try {
await AddonCalendarOffline.instance.deleteEvent(this.eventId!);
CoreDomUtils.instance.triggerFormCancelledEvent(this.formElement, this.currentSite.getId());
this.returnToList();
} catch {
// Shouldn't happen.
CoreDomUtils.instance.showErrorModal('Error discarding event.');
}
} catch {
// Ignore errors
}
}
/**
* Check if we can leave the page or not.
*
* @return Resolved if we can leave it, rejected if not.
*/
async ionViewCanLeave(): Promise<void> {
if (AddonCalendarHelper.instance.hasEventDataChanged(this.form.value, this.originalData)) {
// Show confirmation if some data has been modified.
await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.confirmcanceledit'));
}
CoreDomUtils.instance.triggerFormCancelledEvent(this.formElement, this.currentSite.getId());
}
/**
* Unblock sync.
*/
protected unblockSync(): void {
if (this.eventId) {
CoreSync.instance.unblockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
}
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.unblockSync();
this.isDestroyed = true;
}
}

View File

@ -0,0 +1,11 @@
:host {
.addon-calendar-eventtype-container.item-select-disabled {
ion-label, ion-select {
opacity: 1;
}
ion-select::part(icon) {
display: none;
}
}
}

View File

@ -0,0 +1,169 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title *ngIf="event">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon"></ion-icon>
<core-format-text [text]="event.name" [contextLevel]="event.contextLevel"
[contextInstanceId]="event.contextInstanceId"></core-format-text>
</ion-title>
<ion-buttons slot="end">
<!-- The context menu will be added in here. -->
</ion-buttons>
</ion-toolbar>
</ion-header>
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item [hidden]="isSplitViewOn || !eventLoaded || (!hasOffline && event && !event.deleted) || !isOnline"
[priority]="400" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(undefined, $event, true)"
[iconAction]="syncIcon" [closeOnClick]="false">
</core-context-menu-item>
<core-context-menu-item [hidden]="!canEdit || !event || !event.canedit || event.deleted" [priority]="300"
[content]="'core.edit' | translate" (action)="openEdit()" iconAction="fas-edit">
</core-context-menu-item>
<core-context-menu-item [hidden]="!canDelete || !event || !event.candelete || event.deleted" [priority]="200"
[content]="'core.delete' | translate" (action)="deleteEvent()"
iconAction="fas-trash"></core-context-menu-item>
<core-context-menu-item [hidden]="!event || !event.deleted" [priority]="200" [content]="'core.restore' | translate"
(action)="undoDelete()" iconAction="fas-undo-alt"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
<ion-content>
<ion-refresher slot="fixed" [disabled]="!eventLoaded" (ionRefresh)="doRefresh($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="eventLoaded">
<!-- There is data to be synchronized -->
<ion-card class="core-warning-card" *ngIf="hasOffline || (event && event.deleted)">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>{{ 'core.hasdatatosync' | translate:{$a: 'addon.calendar.calendarevent' | translate} }}</ion-label>
</ion-item>
</ion-card>
<ion-card>
<ion-card-content *ngIf="event">
<ion-item class="ion-text-wrap" *ngIf="isSplitViewOn">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" alt="" role="presentation"
class="core-module-icon">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start">
</ion-icon>
<ion-label>
<h2>{{ 'addon.calendar.eventname' | translate }}</h2>
<p>
<core-format-text [text]="event.name" [contextLevel]="event.contextLevel"
[contextInstanceId]="event.contextInstanceId"></core-format-text>
</p>
</ion-label>
<ion-note slot="end" *ngIf="event.deleted">
<ion-icon name="fas-trash"></ion-icon> {{ 'core.deletedoffline' | translate }}
</ion-note>
</ion-item>
<ion-item>
<ion-label>
<h2>{{ 'addon.calendar.when' | translate }}</h2>
<p [innerHTML]="event.formattedtime"></p>
</ion-label>
<ion-note slot="end" *ngIf="!isSplitViewOn && event.deleted">
<ion-icon name="fas-trash"></ion-icon> {{ 'core.deletedoffline' | translate }}
</ion-note>
</ion-item>
<ion-item>
<ion-label>
<h2>{{ 'addon.calendar.eventtype' | translate }}</h2>
<p>{{ 'addon.calendar.type' + event.formattedType | translate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="courseName" [href]="courseUrl" core-link capture="true">
<ion-label>
<h2>{{ 'core.course' | translate}}</h2>
<p>
<core-format-text [text]="courseName" contextLevel="course" [contextInstanceId]="courseId">
</core-format-text>
</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="groupName">
<ion-label>
<h2>{{ 'core.group' | translate}}</h2>
<p>{{ groupName }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="categoryPath">
<ion-label>
<h2>{{ 'core.category' | translate}}</h2>
<p><core-format-text [text]="categoryPath" contextLevel="coursecat"
[contextInstanceId]="event.categoryid"></core-format-text></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="event.description">
<ion-label>
<h2>{{ 'core.description' | translate}}</h2>
<p>
<core-format-text [text]="event.description" [contextLevel]="event.contextLevel"
[contextInstanceId]="event.contextInstanceId"></core-format-text>
</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="event.location">
<ion-label>
<h2>{{ 'core.location' | translate}}</h2>
<p>
<a [href]="event.encodedLocation" core-link auto-login="no">
<core-format-text [text]="event.location" [contextLevel]="event.contextLevel"
[contextInstanceId]="event.contextInstanceId"></core-format-text>
</a>
</p>
</ion-label>
</ion-item>
<ion-item *ngIf="moduleUrl">
<ion-label>
<ion-button expand="block" color="primary" [href]="moduleUrl" core-link capture="true">
{{ 'addon.calendar.gotoactivity' | translate }}
</ion-button>
</ion-label>
</ion-item>
</ion-card-content>
</ion-card>
<ion-card list *ngIf="notificationsEnabled && event">
<ion-item>
<ion-label>
<h2>{{ 'addon.calendar.reminders' | translate }}</h2>
</ion-label>
</ion-item>
<ng-container *ngFor="let reminder of reminders">
<ion-item *ngIf="reminder.time > 0 || defaultTime > 0" class="ion-text-wrap"
[class.item-dimmed]="(reminder.time == -1 ? (event.timestart - defaultTime) : reminder.time) <= currentTime!">
<ion-label>
<p *ngIf="reminder.time == -1">
{{ 'core.defaultvalue' | translate :{$a: ((event.timestart - defaultTime) * 1000) | coreFormatDate } }}
</p>
<p *ngIf="reminder.time > 0">{{ reminder.time * 1000 | coreFormatDate }}</p>
</ion-label>
<ion-button fill="clear" (click)="cancelNotification(reminder.id, $event)"
[attr.aria-label]=" 'core.delete' | translate" slot="end"
*ngIf="(reminder.time == -1 ? (event.timestart - defaultTime) : reminder.time) > currentTime!">
<ion-icon name="fas-trash" color="danger" slot="icon-only"></ion-icon>
</ion-button>
</ion-item>
</ng-container>
<ng-container *ngIf="event.timestart + event.timeduration > currentTime!">
<ion-item>
<ion-label>
<ion-button expand="block" color="primary" (click)="notificationPicker.open()">
{{ 'addon.calendar.setnewreminder' | translate }}
</ion-button>
</ion-label>
</ion-item>
<ion-datetime #notificationPicker hidden [(ngModel)]="notificationTimeText"
[displayFormat]="notificationFormat" [min]="notificationMin" [max]="notificationMax"
doneText]="'core.add' | translate"(ionChange)="addNotificationTime()">
</ion-datetime>
</ng-container>
</ion-card>
</core-loading>
</ion-content>

View File

@ -0,0 +1,53 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { AddonCalendarComponentsModule } from '../../components/components.module';
import { AddonCalendarEventPage } from './event.page';
const routes: Routes = [
{
path: '',
component: AddonCalendarEventPage,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CommonModule,
IonicModule,
FormsModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
AddonCalendarComponentsModule,
],
declarations: [
AddonCalendarEventPage,
],
exports: [RouterModule],
})
export class AddonCalendarEventPageModule {}

View File

@ -0,0 +1,582 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnDestroy, OnInit } from '@angular/core';
import { IonRefresher } from '@ionic/angular';
import { AlertOptions } from '@ionic/core';
import {
AddonCalendar,
AddonCalendarEvent,
AddonCalendarEventBase,
AddonCalendarEventToDisplay,
AddonCalendarGetEventsEvent,
AddonCalendarProvider,
AddonCalendarUpdatedEventEvent,
} from '../../services/calendar';
import { AddonCalendarHelper } from '../../services/calendar-helper';
import { AddonCalendarOffline } from '../../services/calendar-offline';
import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync';
import { CoreCourses } from '@features/courses/services/courses';
import { CoreApp } from '@services/app';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreSites } from '@services/sites';
import { CoreLocalNotifications } from '@services/local-notifications';
import { CoreCourse } from '@features/course/services/course';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreGroups } from '@services/groups';
// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { Network, NgZone, Translate } from '@singletons';
import { Subscription } from 'rxjs';
import { CoreNavigator } from '@services/navigator';
import { CoreUtils } from '@services/utils/utils';
import { AddonCalendarReminderDBRecord } from '../../services/database/calendar';
import { ActivatedRoute } from '@angular/router';
/**
* Page that displays a single calendar event.
*/
@Component({
selector: 'page-addon-calendar-event',
templateUrl: 'event.html',
styleUrls: ['event.scss'],
})
export class AddonCalendarEventPage implements OnInit, OnDestroy {
protected eventId!: number;
protected siteHomeId: number;
protected editEventObserver: CoreEventObserver;
protected syncObserver: CoreEventObserver;
protected manualSyncObserver: CoreEventObserver;
protected onlineObserver: Subscription;
protected currentSiteId: string;
eventLoaded = false;
notificationFormat?: string;
notificationMin?: string;
notificationMax?: string;
notificationTimeText?: string;
event?: AddonCalendarEventToDisplay;
courseId?: number;
courseName = '';
groupName?: string;
courseUrl = '';
notificationsEnabled = false;
moduleUrl = '';
categoryPath = '';
currentTime?: number;
defaultTime = 0;
reminders: AddonCalendarReminderDBRecord[] = [];
canEdit = false;
canDelete = false;
hasOffline = false;
isOnline = false;
syncIcon = 'spinner'; // Sync icon.
isSplitViewOn = false;
constructor(
protected route: ActivatedRoute,
// @Optional() private svComponent: CoreSplitViewComponent,
) {
this.notificationsEnabled = CoreLocalNotifications.instance.isAvailable();
this.siteHomeId = CoreSites.instance.getCurrentSiteHomeId();
this.currentSiteId = CoreSites.instance.getCurrentSiteId();
// this.isSplitViewOn = this.svComponent && this.svComponent.isOn();
// Check if site supports editing and deleting. No need to check allowed types, event.canedit already does it.
this.canEdit = AddonCalendar.instance.canEditEventsInSite();
this.canDelete = AddonCalendar.instance.canDeleteEventsInSite();
this.asyncConstructor();
// Listen for event edited. If current event is edited, reload the data.
this.editEventObserver = CoreEvents.on(AddonCalendarProvider.EDIT_EVENT_EVENT, (data: AddonCalendarUpdatedEventEvent) => {
if (data && data.eventId == this.eventId) {
this.eventLoaded = false;
this.refreshEvent(true, false);
}
}, this.currentSiteId);
// Refresh data if this calendar event is synchronized automatically.
this.syncObserver = CoreEvents.on(
AddonCalendarSyncProvider.AUTO_SYNCED,
this.checkSyncResult.bind(this, false),
this.currentSiteId,
);
// Refresh data if calendar events are synchronized manually but not by this page.
this.manualSyncObserver = CoreEvents.on(
AddonCalendarSyncProvider.MANUAL_SYNCED,
this.checkSyncResult.bind(this, true),
this.currentSiteId,
);
// Refresh online status when changes.
this.onlineObserver = Network.instance.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.instance.run(() => {
this.isOnline = CoreApp.instance.isOnline();
});
});
}
protected async asyncConstructor(): Promise<void> {
if (this.notificationsEnabled) {
this.reminders = await AddonCalendar.instance.getEventReminders(this.eventId);
this.defaultTime = await AddonCalendar.instance.getDefaultNotificationTime() * 60;
// Calculate format to use.
this.notificationFormat =
CoreTimeUtils.instance.fixFormatForDatetime(CoreTimeUtils.instance.convertPHPToMoment(
Translate.instance.instant('core.strftimedatetime'),
));
}
}
/**
* View loaded.
*/
ngOnInit(): void {
this.eventId = this.route.snapshot.queryParams['id'];
this.syncIcon = 'spinner';
this.fetchEvent();
}
/**
* Fetches the event and updates the view.
*
* @param sync Whether it should try to synchronize offline events.
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
async fetchEvent(sync = false, showErrors = false): Promise<void> {
const currentSite = CoreSites.instance.getCurrentSite();
const canGetById = AddonCalendar.instance.isGetEventByIdAvailableInSite();
let deleted = false;
this.isOnline = CoreApp.instance.isOnline();
if (sync) {
// Try to synchronize offline events.
try {
const result = await AddonCalendarSync.instance.syncEvents();
if (result.warnings && result.warnings.length) {
CoreDomUtils.instance.showErrorModal(result.warnings[0]);
}
if (result.deleted && result.deleted.indexOf(this.eventId) != -1) {
// This event was deleted during the sync.
deleted = true;
}
if (result.updated) {
// Trigger a manual sync event.
result.source = 'event';
CoreEvents.trigger<AddonCalendarSyncEvents>(
AddonCalendarSyncProvider.MANUAL_SYNCED,
result,
this.currentSiteId,
);
}
} catch (error) {
if (showErrors) {
CoreDomUtils.instance.showErrorModalDefault(error, 'core.errorsync', true);
}
}
}
if (deleted) {
return;
}
try {
let event: AddonCalendarEvent | AddonCalendarEventBase | AddonCalendarGetEventsEvent;
// Get the event data.
if (canGetById) {
event = await AddonCalendar.instance.getEventById(this.eventId);
} else {
event = await AddonCalendar.instance.getEvent(this.eventId);
}
this.event = AddonCalendarHelper.instance.formatEventData(event);
try {
const offlineEvent = AddonCalendarHelper.instance.formatOfflineEventData(
await AddonCalendarOffline.instance.getEvent(this.eventId),
);
// There is offline data, apply it.
this.hasOffline = true;
this.event = Object.assign(this.event, offlineEvent);
} catch {
// No offline data.
this.hasOffline = false;
}
this.currentTime = CoreTimeUtils.instance.timestamp();
this.notificationMin = CoreTimeUtils.instance.userDate(this.currentTime * 1000, 'YYYY-MM-DDTHH:mm', false);
this.notificationMax = CoreTimeUtils.instance.userDate(
(this.event!.timestart + this.event!.timeduration) * 1000,
'YYYY-MM-DDTHH:mm',
false,
);
// Reset some of the calculated data.
this.categoryPath = '';
this.courseName = '';
this.courseUrl = '';
this.moduleUrl = '';
if (this.event!.moduleIcon) {
// It's a module event, translate the module name to the current language.
const name = CoreCourse.instance.translateModuleName(this.event!.modulename || '');
if (name.indexOf('core.mod_') === -1) {
this.event!.modulename = name;
}
// Get the module URL.
if (canGetById) {
this.moduleUrl = this.event!.url || '';
}
}
const promises: Promise<void>[] = [];
const courseId = this.event.courseid;
if (courseId != this.siteHomeId) {
// If the event belongs to a course, get the course name and the URL to view it.
if (canGetById && this.event.course) {
this.courseId = this.event.course.id;
this.courseName = this.event.course.fullname;
this.courseUrl = this.event.course.viewurl;
} else if (!canGetById && this.event.courseid ) {
// Retrieve the course.
promises.push(CoreCourses.instance.getUserCourse(this.event.courseid, true).then((course) => {
this.courseId = course.id;
this.courseName = course.fullname;
this.courseUrl = currentSite ? CoreTextUtils.instance.concatenatePaths(
currentSite.siteUrl,
'/course/view.php?id=' + this.courseId,
) : '';
return;
}).catch(() => {
// Error getting course, just don't show the course name.
}));
}
}
// If it's a group event, get the name of the group.
if (courseId && this.event.groupid) {
promises.push(CoreGroups.instance.getUserGroupsInCourse(courseId).then((groups) => {
const group = groups.find((group) => group.id == this.event!.groupid);
this.groupName = group ? group.name : '';
return;
}).catch(() => {
// Error getting groups, just don't show the group name.
this.groupName = '';
}));
}
if (canGetById && this.event.iscategoryevent && this.event.category) {
this.categoryPath = this.event.category.nestedname;
}
if (this.event.location) {
// Build a link to open the address in maps.
this.event.location = CoreTextUtils.instance.decodeHTML(this.event.location);
this.event.encodedLocation = CoreTextUtils.instance.buildAddressURL(this.event.location);
}
// Check if event was deleted in offine.
promises.push(AddonCalendarOffline.instance.isEventDeleted(this.eventId).then((deleted) => {
this.event!.deleted = deleted;
return;
}));
// Re-calculate the formatted time so it uses the device date.
promises.push(AddonCalendar.instance.getCalendarTimeFormat().then(async (timeFormat) => {
this.event!.formattedtime = await AddonCalendar.instance.formatEventTime(this.event!, timeFormat);
return;
}));
await Promise.all(promises);
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevent', true);
}
this.eventLoaded = true;
this.syncIcon = 'fas-sync-alt';
}
/**
* Add a reminder for this event.
*/
async addNotificationTime(): Promise<void> {
if (this.notificationTimeText && this.event && this.event.id) {
let notificationTime = CoreTimeUtils.instance.convertToTimestamp(this.notificationTimeText);
const currentTime = CoreTimeUtils.instance.timestamp();
const minute = Math.floor(currentTime / 60) * 60;
// Check if the notification time is in the same minute as we are, so the notification is triggered.
if (notificationTime >= minute && notificationTime < minute + 60) {
notificationTime = currentTime + 1;
}
await AddonCalendar.instance.addEventReminder(this.event, notificationTime);
this.reminders = await AddonCalendar.instance.getEventReminders(this.eventId);
this.notificationTimeText = undefined;
}
}
/**
* Cancel the selected notification.
*
* @param id Reminder ID.
* @param e Click event.
*/
async cancelNotification(id: number, e: Event): Promise<void> {
e.preventDefault();
e.stopPropagation();
try {
await CoreDomUtils.instance.showDeleteConfirm();
const modal = await CoreDomUtils.instance.showModalLoading('core.deleting', true);
try {
await AddonCalendar.instance.deleteEventReminder(id);
this.reminders = await AddonCalendar.instance.getEventReminders(this.eventId);
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'Error deleting reminder');
} finally {
modal.dismiss();
}
} catch {
// Ignore errors.
}
}
/**
* Refresh the data.
*
* @param refresher Refresher.
* @param done Function to call when done.
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, showErrors= false): Promise<void> {
if (!this.eventLoaded) {
return;
}
await this.refreshEvent(true, showErrors).finally(() => {
refresher?.detail.complete();
done && done();
});
}
/**
* Refresh the event.
*
* @param sync Whether it should try to synchronize offline events.
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
async refreshEvent(sync = false, showErrors = false): Promise<void> {
this.syncIcon = 'spinner';
const promises: Promise<void>[] = [];
promises.push(AddonCalendar.instance.invalidateEvent(this.eventId));
promises.push(AddonCalendar.instance.invalidateTimeFormat());
await CoreUtils.instance.allPromisesIgnoringErrors(promises);
await this.fetchEvent(sync, showErrors);
}
/**
* Open the page to edit the event.
*/
openEdit(): void {
// Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav.
// @todo const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
CoreNavigator.instance.navigateToSitePath('/calendar/edit', { params: { eventId: this.eventId } });
}
/**
* Delete the event.
*/
async deleteEvent(): Promise<void> {
if (!this.event) {
return;
}
const title = Translate.instance.instant('addon.calendar.deleteevent');
const options: AlertOptions = {};
let message: string;
if (this.event.eventcount > 1) {
// It's a repeated event.
message = Translate.instance.instant(
'addon.calendar.confirmeventseriesdelete',
{ $a: { name: this.event.name, count: this.event.eventcount } },
);
options.inputs = [
{
type: 'radio',
name: 'deleteall',
checked: true,
value: false,
label: Translate.instance.instant('addon.calendar.deleteoneevent'),
},
{
type: 'radio',
name: 'deleteall',
checked: false,
value: true,
label: Translate.instance.instant('addon.calendar.deleteallevents'),
},
];
} else {
// Not repeated, display a simple confirm.
message = Translate.instance.instant('addon.calendar.confirmeventdelete', { $a: this.event.name });
}
let deleteAll = false;
try {
deleteAll = await CoreDomUtils.instance.showConfirm(message, title, undefined, undefined, options);
} catch {
// User canceled.
return;
}
const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
try {
const sent = await AddonCalendar.instance.deleteEvent(this.event.id, this.event.name, deleteAll);
if (sent) {
// Event deleted, invalidate right days & months.
try {
await AddonCalendarHelper.instance.refreshAfterChangeEvent(this.event, deleteAll ? this.event.eventcount : 1);
} catch {
// Ignore errors.
}
}
// Trigger an event.
CoreEvents.trigger<AddonCalendarUpdatedEventEvent>(AddonCalendarProvider.DELETED_EVENT_EVENT, {
eventId: this.eventId,
sent: sent,
}, CoreSites.instance.getCurrentSiteId());
if (sent) {
CoreDomUtils.instance.showToast('addon.calendar.eventcalendareventdeleted', true, 3000);
// Event deleted, close the view.
/* if (!this.svComponent || !this.svComponent.isOn()) {
this.navCtrl.pop();
}*/
} else {
// Event deleted in offline, just mark it as deleted.
this.event.deleted = true;
}
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'Error deleting event.');
}
modal.dismiss();
}
/**
* Undo delete the event.
*/
async undoDelete(): Promise<void> {
if (!this.event) {
return;
}
const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
try {
await AddonCalendarOffline.instance.unmarkDeleted(this.event.id);
// Trigger an event.
CoreEvents.trigger<AddonCalendarUpdatedEventEvent>(AddonCalendarProvider.UNDELETED_EVENT_EVENT, {
eventId: this.eventId,
}, CoreSites.instance.getCurrentSiteId());
this.event.deleted = false;
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'Error undeleting event.');
}
modal.dismiss();
}
/**
* Check the result of an automatic sync or a manual sync not done by this page.
*
* @param isManual Whether it's a manual sync.
* @param data Sync result.
*/
protected checkSyncResult(isManual: boolean, data: AddonCalendarSyncEvents): void {
if (!data) {
return;
}
if (data.deleted && data.deleted.indexOf(this.eventId) != -1) {
CoreDomUtils.instance.showToast('addon.calendar.eventcalendareventdeleted', true, 3000);
// Event was deleted, close the view.
/* if (!this.svComponent || !this.svComponent.isOn()) {
this.navCtrl.pop();
}*/
} else if (data.events && (!isManual || data.source != 'event')) {
const event = data.events.find((ev) => ev.id == this.eventId);
if (event) {
this.eventLoaded = false;
this.refreshEvent();
}
}
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.editEventObserver?.off();
this.syncObserver?.off();
this.manualSyncObserver?.off();
this.onlineObserver?.unsubscribe();
}
}

View File

@ -0,0 +1,9 @@
:host {
ion-card ion-note {
font-size: 1.6rem;
}
ion-title ion-icon, ion-title img {
margin-left: 10px;
margin-right: 10px;
}
}

View File

@ -0,0 +1,55 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>{{ (showCalendar ? 'addon.calendar.calendarevents' : 'addon.calendar.upcomingevents') | translate }}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="openFilter($event)" [attr.aria-label]="'core.filter' | translate">
<ion-icon slot="icon-only" name="fas-filter"></ion-icon>
</ion-button>
<core-context-menu>
<core-context-menu-item *ngIf="showCalendar" [priority]="800"
[content]="'addon.calendar.upcomingevents' | translate" iconAction="fas-th-list"
(action)="toggleDisplay()"></core-context-menu-item>
<core-context-menu-item *ngIf="!showCalendar" [priority]="800"
[content]="'addon.calendar.monthlyview' | translate" iconAction="fas-calendar-alt"
(action)="toggleDisplay()"></core-context-menu-item>
<core-context-menu-item [hidden]="!notificationsEnabled" [priority]="600"
[content]="'core.settings.settings' | translate" (action)="openSettings()" iconAction="fas-cogs">
</core-context-menu-item>
<core-context-menu-item [hidden]="!loaded || !hasOffline || !isOnline" [priority]="400"
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(undefined, $event, true)"
[iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
</core-context-menu>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<!-- There is data to be synchronized -->
<ion-card class="core-warning-card" *ngIf="hasOffline">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>{{ 'core.hasdatatosync' | translate:{$a: 'addon.calendar.calendar' | translate} }}</ion-label>
</ion-item>
</ion-card>
<addon-calendar-calendar [hidden]="!showCalendar" [initialYear]="year" [initialMonth]="month" [filter]="filter"
[displayNavButtons]="showCalendar" (onEventClicked)="gotoEvent($event)" (onDayClicked)="gotoDay($event)">
</addon-calendar-calendar>
<addon-calendar-upcoming-events *ngIf="loadUpcoming" [hidden]="showCalendar" [filter]="filter"
(onEventClicked)="gotoEvent($event)">
</addon-calendar-upcoming-events>
<!-- Create a calendar event. -->
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canCreate">
<ion-fab-button (click)="openEdit()" [attr.aria-label]="'addon.calendar.newevent' | translate">
<ion-icon name="fas-plus"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { AddonCalendarComponentsModule } from '../../components/components.module';
import { AddonCalendarIndexPage } from './index.page';
const routes: Routes = [
{
path: '',
component: AddonCalendarIndexPage,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
AddonCalendarComponentsModule,
],
declarations: [
AddonCalendarIndexPage,
],
exports: [RouterModule],
})
export class AddonCalendarIndexPageModule {}

View File

@ -0,0 +1,406 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { PopoverController, IonRefresher } from '@ionic/angular';
import { CoreApp } from '@services/app';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreCoursesHelper } from '@features/courses/services/courses-helper';
import { AddonCalendar, AddonCalendarProvider, AddonCalendarUpdatedEventEvent } from '../../services/calendar';
import { AddonCalendarOffline } from '../../services/calendar-offline';
import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync';
import { AddonCalendarFilter, AddonCalendarHelper } from '../../services/calendar-helper';
import { Network, NgZone } from '@singletons';
import { Subscription } from 'rxjs';
import { CoreEnrolledCourseData } from '@features/courses/services/courses';
import { ActivatedRoute, Params } from '@angular/router';
import { AddonCalendarCalendarComponent } from '../../components/calendar/calendar';
import { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming-events/upcoming-events';
import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter';
import { CoreNavigator } from '@services/navigator';
import { CoreLocalNotifications } from '@services/local-notifications';
/**
* Page that displays the calendar events.
*/
@Component({
selector: 'page-addon-calendar-index',
templateUrl: 'index.html',
})
export class AddonCalendarIndexPage implements OnInit, OnDestroy {
@ViewChild(AddonCalendarCalendarComponent) calendarComponent?: AddonCalendarCalendarComponent;
@ViewChild(AddonCalendarUpcomingEventsComponent) upcomingEventsComponent?: AddonCalendarUpcomingEventsComponent;
protected eventId?: number;
protected currentSiteId: string;
// Observers.
protected newEventObserver?: CoreEventObserver;
protected discardedObserver?: CoreEventObserver;
protected editEventObserver?: CoreEventObserver;
protected deleteEventObserver?: CoreEventObserver;
protected undeleteEventObserver?: CoreEventObserver;
protected syncObserver?: CoreEventObserver;
protected manualSyncObserver?: CoreEventObserver;
protected onlineObserver?: Subscription;
protected filterChangedObserver?: CoreEventObserver;
year?: number;
month?: number;
canCreate = false;
courses: Partial<CoreEnrolledCourseData>[] = [];
notificationsEnabled = false;
loaded = false;
hasOffline = false;
isOnline = false;
syncIcon = 'spinner';
showCalendar = true;
loadUpcoming = false;
filter: AddonCalendarFilter = {
filtered: false,
courseId: -1,
categoryId: undefined,
course: true,
group: true,
site: true,
user: true,
category: true,
};
constructor(
protected popoverCtrl: PopoverController,
protected route: ActivatedRoute,
) {
this.currentSiteId = CoreSites.instance.getCurrentSiteId();
// Listen for events added. When an event is added, reload the data.
this.newEventObserver = CoreEvents.on(
AddonCalendarProvider.NEW_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (data && data.eventId) {
this.loaded = false;
this.refreshData(true, false);
}
},
this.currentSiteId,
);
// Listen for new event discarded event. When it does, reload the data.
this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
this.loaded = false;
this.refreshData(true, false);
}, this.currentSiteId);
// Listen for events edited. When an event is edited, reload the data.
this.editEventObserver = CoreEvents.on(
AddonCalendarProvider.EDIT_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (data && data.eventId) {
this.loaded = false;
this.refreshData(true, false);
}
},
this.currentSiteId,
);
// Refresh data if calendar events are synchronized automatically.
this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => {
this.loaded = false;
this.refreshData(false, false);
}, this.currentSiteId);
// Refresh data if calendar events are synchronized manually but not by this page.
this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data: AddonCalendarSyncEvents) => {
if (data && data.source != 'index') {
this.loaded = false;
this.refreshData(false, false);
}
}, this.currentSiteId);
// Update the events when an event is deleted.
this.deleteEventObserver = CoreEvents.on(AddonCalendarProvider.DELETED_EVENT_EVENT, () => {
this.loaded = false;
this.refreshData(false, false);
}, this.currentSiteId);
// Update the "hasOffline" property if an event deleted in offline is restored.
this.undeleteEventObserver = CoreEvents.on(AddonCalendarProvider.UNDELETED_EVENT_EVENT, async () => {
this.hasOffline = await AddonCalendarOffline.instance.hasOfflineData();
}, this.currentSiteId);
this.filterChangedObserver = CoreEvents.on(
AddonCalendarProvider.FILTER_CHANGED_EVENT,
async (filterData: AddonCalendarFilter) => {
this.filter = filterData;
// Course viewed has changed, check if the user can create events for this course calendar.
this.canCreate = await AddonCalendarHelper.instance.canEditEvents(this.filter['courseId']);
},
);
// Refresh online status when changes.
this.onlineObserver = Network.instance.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.instance.run(() => {
this.isOnline = CoreApp.instance.isOnline();
});
});
}
/**
* View loaded.
*/
ngOnInit(): void {
this.notificationsEnabled = CoreLocalNotifications.instance.isAvailable();
this.route.queryParams.subscribe(params => {
this.eventId = parseInt(params['eventId'], 10) || undefined;
this.filter.courseId = parseInt(params['courseId'], 10) || -1;
this.year = parseInt(params['year'], 10) || undefined;
this.month = parseInt(params['month'], 10) || undefined;
this.loadUpcoming = !!params['upcoming'];
this.showCalendar = !this.loadUpcoming;
this.filter.filtered = this.filter.courseId > 0;
if (this.eventId) {
// There is an event to load, open the event in a new state.
this.gotoEvent(this.eventId);
}
this.fetchData(true, false);
});
}
/**
* Fetch all the data required for the view.
*
* @param sync Whether it should try to synchronize offline events.
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
async fetchData(sync?: boolean, showErrors?: boolean): Promise<void> {
this.syncIcon = 'spinner';
this.isOnline = CoreApp.instance.isOnline();
if (sync) {
// Try to synchronize offline events.
try {
const result = await AddonCalendarSync.instance.syncEvents();
if (result.warnings && result.warnings.length) {
CoreDomUtils.instance.showErrorModal(result.warnings[0]);
}
if (result.updated) {
// Trigger a manual sync event.
result.source = 'index';
CoreEvents.trigger<AddonCalendarSyncEvents>(
AddonCalendarSyncProvider.MANUAL_SYNCED,
result,
this.currentSiteId,
);
}
} catch (error) {
if (showErrors) {
CoreDomUtils.instance.showErrorModalDefault(error, 'core.errorsync', true);
}
}
}
try {
const promises: Promise<void>[] = [];
this.hasOffline = false;
// Load courses for the popover.
promises.push(CoreCoursesHelper.instance.getCoursesForPopover(this.filter.courseId).then((data) => {
this.courses = data.courses;
return;
}));
// Check if user can create events.
promises.push(AddonCalendarHelper.instance.canEditEvents(this.filter.courseId).then((canEdit) => {
this.canCreate = canEdit;
return;
}));
// Check if there is offline data.
promises.push(AddonCalendarOffline.instance.hasOfflineData().then((hasOffline) => {
this.hasOffline = hasOffline;
return;
}));
await Promise.all(promises);
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}
this.loaded = true;
this.syncIcon = 'fas-sync-alt';
}
/**
* Refresh the data.
*
* @param refresher Refresher.
* @param done Function to call when done.
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, showErrors?: boolean): Promise<void> {
if (!this.loaded) {
return;
}
await this.refreshData(true, showErrors).finally(() => {
refresher?.detail.complete();
done && done();
});
}
/**
* Refresh the data.
*
* @param sync Whether it should try to synchronize offline events.
* @param showErrors Whether to show sync errors to the user.
* @param afterChange Whether the refresh is done after an event has changed or has been synced.
* @return Promise resolved when done.
*/
async refreshData(sync = false, showErrors = false): Promise<void> {
this.syncIcon = 'spinner';
const promises: Promise<void>[] = [];
promises.push(AddonCalendar.instance.invalidateAllowedEventTypes());
// Refresh the sub-component.
if (this.showCalendar && this.calendarComponent) {
promises.push(this.calendarComponent.refreshData());
} else if (!this.showCalendar && this.upcomingEventsComponent) {
promises.push(this.upcomingEventsComponent.refreshData());
}
await Promise.all(promises).finally(() => this.fetchData(sync, showErrors));
}
/**
* Navigate to a particular event.
*
* @param eventId Event to load.
*/
gotoEvent(eventId: number): void {
if (eventId < 0) {
// It's an offline event, go to the edit page.
this.openEdit(eventId);
} else {
CoreNavigator.instance.navigateToSitePath('/calendar/event', { params: { id: eventId } });
}
}
/**
* View a certain day.
*
* @param data Data with the year, month and day.
*/
gotoDay(data: {day: number; month: number; year: number}): void {
const params: Params = {
day: data.day,
month: data.month,
year: data.year,
};
Object.keys(this.filter).forEach((key) => {
params[key] = this.filter[key];
});
CoreNavigator.instance.navigateToSitePath('/calendar/day', { params });
}
/**
* Show the context menu.
*
* @param event Event.
*/
async openFilter(event: MouseEvent): Promise<void> {
const popover = await this.popoverCtrl.create({
component: AddonCalendarFilterPopoverComponent,
componentProps: {
courses: this.courses,
filter: this.filter,
},
event,
});
await popover.present();
}
/**
* Open page to create/edit an event.
*
* @param eventId Event ID to edit.
*/
openEdit(eventId?: number): void {
const params: Params = {};
if (eventId) {
params.eventId = eventId;
}
if (this.filter.courseId) {
params.courseId = this.filter.courseId;
}
CoreNavigator.instance.navigateToSitePath('/calendar/edit', { params });
}
/**
* Open calendar events settings.
*/
openSettings(): void {
CoreNavigator.instance.navigateToSitePath('/calendar/settings');
}
/**
* Toogle display: monthly view or upcoming events.
*/
toggleDisplay(): void {
this.showCalendar = !this.showCalendar;
if (!this.showCalendar) {
this.loadUpcoming = true;
}
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.newEventObserver?.off();
this.discardedObserver?.off();
this.editEventObserver?.off();
this.deleteEventObserver?.off();
this.undeleteEventObserver?.off();
this.syncObserver?.off();
this.manualSyncObserver?.off();
this.filterChangedObserver?.off();
this.onlineObserver?.unsubscribe();
}
}

View File

@ -0,0 +1,90 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>{{ 'addon.calendar.calendarevents' | translate }}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="openFilter($event)" [attr.aria-label]="'core.filter' | translate">
<ion-icon slot="icon-only" name="fas-filter"></ion-icon>
</ion-button>
<core-context-menu>
<core-context-menu-item [hidden]="!notificationsEnabled" [priority]="600"
[content]="'core.settings.settings' | translate" (action)="openSettings()" iconAction="fas-cogs">
</core-context-menu-item>
<core-context-menu-item [hidden]="!eventsLoaded || !hasOffline || !isOnline" [priority]="400"
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(undefined, $event, true)"
[iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
</core-context-menu>
</ion-buttons>
</ion-toolbar>
</ion-header>
<!--<core-split-view>-->
<ion-content>
<ion-refresher slot="fixed" [disabled]="!eventsLoaded" (ionRefresh)="doRefresh($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="eventsLoaded">
<!-- There is data to be synchronized -->
<ion-card class="core-warning-card" *ngIf="hasOffline">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<ion-label>{{ 'core.hasdatatosync' | translate:{$a: 'addon.calendar.calendar' | translate} }}</ion-label>
</ion-item>
</ion-card>
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="fas-calendar"
[message]="'addon.calendar.noevents' | translate">
</core-empty-box>
<ion-list *ngIf="filteredEvents && filteredEvents.length" class="ion-no-margin">
<ng-container *ngFor="let event of filteredEvents">
<ion-item-divider *ngIf="event.showDate">
<ion-label>{{ event.timestart * 1000 | coreFormatDate: "strftimedayshort" }}</ion-label>
</ion-item-divider>
<ion-item class="ion-text-wrap" [title]="event.name" (click)="gotoEvent(event.id)"
[class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event"
[ngClass]="['addon-calendar-eventtype-'+event.eventtype]">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon">
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start">
</ion-icon>
<ion-label>
<h2>
<core-format-text [text]="event.name" [contextLevel]="event.contextLevel"
[contextInstanceId]="event.contextInstanceId">
</core-format-text>
</h2>
<p>
{{ event.timestart * 1000 | coreFormatDate: "strftimetime" }}
<span *ngIf="event.timeduration && event.endsSameDay">
- {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimetime" }}
</span>
<span *ngIf="event.timeduration && !event.endsSameDay">
- {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimedatetimeshort" }}
</span>
</p>
</ion-label>
<ion-note *ngIf="event.offline && !event.deleted" slot="end">
<ion-icon name="far-clock"></ion-icon>
<span class="ion-text-wrap">{{ 'core.notsent' | translate }}</span>
</ion-note>
<ion-note *ngIf="event.deleted" slot="end">
<ion-icon name="fas-trash"></ion-icon>
<span class="ion-text-wrap">{{ 'core.deletedoffline' | translate }}</span>
</ion-note>
</ion-item>
</ng-container>
</ion-list>
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreEvents($event)" [error]="loadMoreError">
</core-infinite-loading>
</core-loading>
<!-- Create a calendar event. -->
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canCreate">
<ion-fab-button (click)="openEdit()" [attr.aria-label]="'addon.calendar.newevent' | translate">
<ion-icon name="fas-plus"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
<!--</core-split-view>-->

View File

@ -0,0 +1,49 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { AddonCalendarListPage } from './list.page';
const routes: Routes = [
{
path: '',
component: AddonCalendarListPage,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
],
declarations: [
AddonCalendarListPage,
],
exports: [RouterModule],
})
export class AddonCalendarListPageModule {}

View File

@ -0,0 +1,704 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core';
import { PopoverController, IonContent, IonRefresher } from '@ionic/angular';
import {
AddonCalendarProvider,
AddonCalendar,
AddonCalendarEventToDisplay,
AddonCalendarUpdatedEventEvent,
} from '../../services/calendar';
import { AddonCalendarOffline } from '../../services/calendar-offline';
import { AddonCalendarFilter, AddonCalendarHelper } from '../../services/calendar-helper';
import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync';
import { CoreCategoryData, CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreSites } from '@services/sites';
import { CoreLocalNotifications } from '@services/local-notifications';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreApp } from '@services/app';
// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view';
import moment from 'moment';
import { CoreConstants } from '@/core/constants';
import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter';
import { ActivatedRoute, Params } from '@angular/router';
import { Subscription } from 'rxjs';
import { Network, NgZone } from '@singletons';
import { CoreCoursesHelper } from '@features/courses/services/courses-helper';
import { CoreUtils } from '@services/utils/utils';
import { CoreNavigator } from '@services/navigator';
/**
* Page that displays the list of calendar events.
*/
@Component({
selector: 'page-addon-calendar-list',
templateUrl: 'list.html',
styleUrls: ['../../calendar-common.scss', 'list.scss'],
})
export class AddonCalendarListPage implements OnInit, OnDestroy {
@ViewChild(IonContent) content?: IonContent;
// @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
protected initialTime = 0;
protected daysLoaded = 0;
protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events.
protected categoriesRetrieved = false;
protected getCategories = false;
protected categories: { [id: number]: CoreCategoryData } = {};
protected siteHomeId: number;
protected currentSiteId: string;
protected onlineEvents: AddonCalendarEventToDisplay[] = [];
protected offlineEvents: AddonCalendarEventToDisplay[] = [];
protected deletedEvents: number [] = [];
// Observers.
protected obsDefaultTimeChange?: CoreEventObserver;
protected newEventObserver: CoreEventObserver;
protected discardedObserver: CoreEventObserver;
protected editEventObserver: CoreEventObserver;
protected deleteEventObserver: CoreEventObserver;
protected undeleteEventObserver: CoreEventObserver;
protected syncObserver: CoreEventObserver;
protected manualSyncObserver: CoreEventObserver;
protected filterChangedObserver: CoreEventObserver;
protected onlineObserver: Subscription;
eventId?: number; // Selected EventId on list
courses: Partial<CoreEnrolledCourseData>[] = [];
eventsLoaded = false;
events: AddonCalendarEventToDisplay[] = []; // Events (both online and offline).
notificationsEnabled = false;
filteredEvents: AddonCalendarEventToDisplay[] = [];
canLoadMore = false;
loadMoreError = false;
canCreate = false;
hasOffline = false;
isOnline = false;
syncIcon = 'spinner';
filter: AddonCalendarFilter = {
filtered: false,
courseId: -1,
categoryId: undefined,
course: true,
group: true,
site: true,
user: true,
category: true,
};
constructor(
protected route: ActivatedRoute,
private popoverCtrl: PopoverController,
) {
this.siteHomeId = CoreSites.instance.getCurrentSiteHomeId();
this.notificationsEnabled = CoreLocalNotifications.instance.isAvailable();
this.currentSiteId = CoreSites.instance.getCurrentSiteId();
if (this.notificationsEnabled) {
// Re-schedule events if default time changes.
this.obsDefaultTimeChange = CoreEvents.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
AddonCalendar.instance.scheduleEventsNotifications(this.onlineEvents);
}, this.currentSiteId);
}
// Listen for events added. When an event is added, reload the data.
this.newEventObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data: AddonCalendarUpdatedEventEvent) => {
if (data && data.eventId) {
/* if (this.splitviewCtrl.isOn()) {
// Discussion added, clear details page.
this.splitviewCtrl.emptyDetails();
}*/
this.eventsLoaded = false;
this.refreshEvents(true, false).finally(() => {
// In tablet mode try to open the event (only if it's an online event).
/* if (this.splitviewCtrl.isOn() && data.event.id > 0) {
this.gotoEvent(data.event.id);
}*/
});
}
}, this.currentSiteId);
// Listen for new event discarded event. When it does, reload the data.
this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
/* if (this.splitviewCtrl.isOn()) {
// Discussion added, clear details page.
this.splitviewCtrl.emptyDetails();
}*/
this.eventsLoaded = false;
this.refreshEvents(true, false);
}, this.currentSiteId);
// Listen for events edited. When an event is edited, reload the data.
this.editEventObserver = CoreEvents.on(AddonCalendarProvider.EDIT_EVENT_EVENT, (data: AddonCalendarUpdatedEventEvent) => {
if (data && data.eventId) {
this.eventsLoaded = false;
this.refreshEvents(true, false);
}
}, this.currentSiteId);
// Refresh data if calendar events are synchronized automatically.
this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => {
this.eventsLoaded = false;
this.refreshEvents();
/* if (this.splitviewCtrl.isOn() && this.eventId && data && data.deleted && data.deleted.indexOf(this.eventId) != -1) {
// Current selected event was deleted. Clear details.
this.splitviewCtrl.emptyDetails();
} */
}, this.currentSiteId);
// Refresh data if calendar events are synchronized manually but not by this page.
this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data: AddonCalendarSyncEvents) => {
if (data && data.source != 'list') {
this.eventsLoaded = false;
this.refreshEvents();
}
/* if (this.splitviewCtrl.isOn() && this.eventId && data && data.deleted && data.deleted.indexOf(this.eventId) != -1) {
// Current selected event was deleted. Clear details.
this.splitviewCtrl.emptyDetails();
}*/
}, this.currentSiteId);
// Update the list when an event is deleted.
this.deleteEventObserver = CoreEvents.on(
AddonCalendarProvider.DELETED_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (data && !data.sent) {
// Event was deleted in offline. Just mark it as deleted, no need to refresh.
this.markAsDeleted(data.eventId, true);
this.deletedEvents.push(data.eventId);
this.hasOffline = true;
} else {
// Event deleted, clear the details if needed and refresh the view.
/* if (this.splitviewCtrl.isOn()) {
this.splitviewCtrl.emptyDetails();
} */
this.eventsLoaded = false;
this.refreshEvents();
}
},
this.currentSiteId,
);
// Listen for events "undeleted" (offline).
this.undeleteEventObserver = CoreEvents.on(
AddonCalendarProvider.UNDELETED_EVENT_EVENT,
(data: AddonCalendarUpdatedEventEvent) => {
if (!data || !data.eventId) {
return;
}
// Mark it as undeleted, no need to refresh.
this.markAsDeleted(data.eventId, false);
// Remove it from the list of deleted events if it's there.
const index = this.deletedEvents.indexOf(data.eventId);
if (index != -1) {
this.deletedEvents.splice(index, 1);
}
this.hasOffline = !!this.offlineEvents.length || !!this.deletedEvents.length;
},
this.currentSiteId,
);
this.filterChangedObserver =
CoreEvents.on(AddonCalendarProvider.FILTER_CHANGED_EVENT, async (data: AddonCalendarFilter) => {
this.filter = data;
// Course viewed has changed, check if the user can create events for this course calendar.
this.canCreate = await AddonCalendarHelper.instance.canEditEvents(this.filter.courseId);
this.filterEvents();
this.content?.scrollToTop();
});
// Refresh online status when changes.
this.onlineObserver = Network.instance.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.instance.run(() => {
this.isOnline = CoreApp.instance.isOnline();
});
});
}
/**
* View loaded.
*/
async ngOnInit(): Promise<void> {
this.eventId = this.route.snapshot.queryParams['eventId'] || undefined;
this.filter.courseId = this.route.snapshot.queryParams['courseId'];
if (this.eventId) {
// There is an event to load, open the event in a new state.
this.gotoEvent(this.eventId);
}
this.syncIcon = 'spinner';
await this.fetchData(false, true, false);
/* if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) {
// Take first online event and load it. If no online event, load the first offline.
if (this.onlineEvents[0]) {
this.gotoEvent(this.onlineEvents[0].id);
} else {
this.gotoEvent(this.offlineEvents[0].id);
}
}*/
}
/**
* Fetch all the data required for the view.
*
* @param refresh Empty events array first.
* @param sync Whether it should try to synchronize offline events.
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
async fetchData(refresh = false, sync = false, showErrors = false): Promise<void> {
this.initialTime = CoreTimeUtils.instance.timestamp();
this.daysLoaded = 0;
this.emptyEventsTimes = 0;
this.isOnline = CoreApp.instance.isOnline();
if (sync) {
// Try to synchronize offline events.
try {
const result = await AddonCalendarSync.instance.syncEvents();
if (result.warnings && result.warnings.length) {
CoreDomUtils.instance.showErrorModal(result.warnings[0]);
}
if (result.updated) {
// Trigger a manual sync event.
result.source = 'list';
CoreEvents.trigger<AddonCalendarSyncEvents>(
AddonCalendarSyncProvider.MANUAL_SYNCED,
result,
this.currentSiteId,
);
}
} catch (error) {
if (showErrors) {
CoreDomUtils.instance.showErrorModalDefault(error, 'core.errorsync', true);
}
}
}
try {
const promises: Promise<void>[] = [];
this.hasOffline = false;
promises.push(AddonCalendarHelper.instance.canEditEvents(this.filter.courseId).then((canEdit) => {
this.canCreate = canEdit;
return;
}));
// Load courses for the popover.
promises.push(CoreCoursesHelper.instance.getCoursesForPopover(this.filter.courseId).then((result) => {
this.courses = result.courses;
return this.fetchEvents(refresh);
}));
// Get offline events.
promises.push(AddonCalendarOffline.instance.getAllEditedEvents().then((offlineEvents) => {
this.hasOffline = this.hasOffline || !!offlineEvents.length;
// Format data and sort by timestart.
const events: AddonCalendarEventToDisplay[] = offlineEvents.map((event) =>
AddonCalendarHelper.instance.formatOfflineEventData(event));
this.offlineEvents = AddonCalendarHelper.instance.sortEvents(events);
return;
}));
// Get events deleted in offline.
promises.push(AddonCalendarOffline.instance.getAllDeletedEventsIds().then((ids) => {
this.hasOffline = this.hasOffline || !!ids.length;
this.deletedEvents = ids;
return;
}));
await Promise.all(promises);
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}
this.eventsLoaded = true;
this.syncIcon = 'fas-sync-alt';
}
/**
* Fetches the events and updates the view.
*
* @param refresh Empty events array first.
* @return Promise resolved when done.
*/
async fetchEvents(refresh = false): Promise<void> {
this.loadMoreError = false;
try {
const onlineEventsTemp =
await AddonCalendar.instance.getEventsList(this.initialTime, this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL);
if (onlineEventsTemp.length === 0) {
this.emptyEventsTimes++;
if (this.emptyEventsTimes > 5) { // Stop execution if we retrieve empty list 6 consecutive times.
this.canLoadMore = false;
if (refresh) {
this.onlineEvents = [];
this.filteredEvents = [];
this.events = this.offlineEvents;
}
} else {
// No events returned, load next events.
this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
return this.fetchEvents();
}
} else {
const onlineEvents = onlineEventsTemp.map((event) => AddonCalendarHelper.instance.formatEventData(event));
// Get the merged events of this period.
const events = this.mergeEvents(onlineEvents);
this.getCategories = this.shouldLoadCategories(onlineEvents);
if (refresh) {
this.onlineEvents = onlineEvents;
this.events = events;
} else {
// Filter events with same ID. Repeated events are returned once per WS call, show them only once.
this.onlineEvents = CoreUtils.instance.mergeArraysWithoutDuplicates(this.onlineEvents, onlineEvents, 'id');
this.events = CoreUtils.instance.mergeArraysWithoutDuplicates(this.events, events, 'id');
}
this.filterEvents();
// Calculate which evemts need to display the date.
this.filteredEvents.forEach((event, index) => {
event.showDate = this.showDate(event, this.filteredEvents[index - 1]);
event.endsSameDay = this.endsSameDay(event);
});
this.canLoadMore = true;
// Schedule notifications for the events retrieved (might have new events).
AddonCalendar.instance.scheduleEventsNotifications(this.onlineEvents);
this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
}
// Resize the content so infinite loading is able to calculate if it should load more items or not.
// @todo: Infinite loading is not working if content is not high enough.
// this.content.resize();
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
}
// Success retrieving events. Get categories if needed.
if (this.getCategories) {
this.getCategories = false;
return this.loadCategories();
}
}
/**
* Function to load more events.
*
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
* @return Resolved when done.
*/
loadMoreEvents(infiniteComplete?: () => void ): void {
this.fetchEvents().finally(() => {
infiniteComplete && infiniteComplete();
});
}
protected filterEvents(): void {
this.filteredEvents = AddonCalendarHelper.instance.getFilteredEvents(this.events, this.filter, this.categories);
}
/**
* Returns if the current state should load categories or not.
*
* @param events Events to parse.
* @return True if categories should be loaded.
*/
protected shouldLoadCategories(events: AddonCalendarEventToDisplay[]): boolean {
if (this.categoriesRetrieved || this.getCategories) {
// Use previous value
return this.getCategories;
}
// Categories not loaded yet. We should get them if there's any category event.
const found = events.some((event) => typeof event.categoryid != 'undefined' && event.categoryid > 0);
return found || this.getCategories;
}
/**
* Load categories to be able to filter events.
*
* @return Promise resolved when done.
*/
protected async loadCategories(): Promise<void> {
try {
const cats = await CoreCourses.instance.getCategories(0, true);
this.categoriesRetrieved = true;
this.categories = {};
// Index categories by ID.
cats.forEach((category) => {
this.categories[category.id] = category;
});
} catch {
// Ignore errors.
}
}
/**
* Merge a period of online events with the offline events of that period.
*
* @param onlineEvents Online events.
* @return Merged events.
*/
protected mergeEvents(onlineEvents: AddonCalendarEventToDisplay[]): AddonCalendarEventToDisplay[] {
if (!this.offlineEvents.length && !this.deletedEvents.length) {
// No offline events, nothing to merge.
return onlineEvents;
}
const start = this.initialTime + (CoreConstants.SECONDS_DAY * this.daysLoaded);
const end = start + (CoreConstants.SECONDS_DAY * AddonCalendarProvider.DAYS_INTERVAL) - 1;
let result = onlineEvents;
if (this.deletedEvents.length) {
// Mark as deleted the events that were deleted in offline.
result.forEach((event) => {
event.deleted = this.deletedEvents.indexOf(event.id) != -1;
});
}
if (this.offlineEvents.length) {
// Remove the online events that were modified in offline.
result = result.filter((event) => {
const offlineEvent = this.offlineEvents.find((ev) => ev.id == event.id);
return !offlineEvent;
});
}
// Now get the offline events that belong to this period.
const periodOfflineEvents = this.offlineEvents.filter((event) => {
if (this.daysLoaded == 0 && event.timestart < start) {
// Display offline events that are previous to current time to allow editing them.
return true;
}
return (event.timestart >= start || event.timestart + event.timeduration >= start) && event.timestart <= end;
});
// Merge both arrays and sort them.
result = result.concat(periodOfflineEvents);
return AddonCalendarHelper.instance.sortEvents(result);
}
/**
* Refresh the data.
*
* @param refresher Refresher.
* @param done Function to call when done.
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, showErrors?: boolean): Promise<void> {
if (!this.eventsLoaded) {
return;
}
await this.refreshEvents(true, showErrors).finally(() => {
refresher?.detail.complete();
done && done();
});
}
/**
* Refresh the events.
*
* @param sync Whether it should try to synchronize offline events.
* @param showErrors Whether to show sync errors to the user.
* @return Promise resolved when done.
*/
async refreshEvents(sync?: boolean, showErrors?: boolean): Promise<void> {
this.syncIcon = 'spinner';
const promises: Promise<void>[] = [];
promises.push(AddonCalendar.instance.invalidateEventsList());
promises.push(AddonCalendar.instance.invalidateAllowedEventTypes());
if (this.categoriesRetrieved) {
promises.push(CoreCourses.instance.invalidateCategories(0, true));
this.categoriesRetrieved = false;
}
await Promise.all(promises).finally(() => this.fetchData(true, sync, showErrors));
}
/**
* Check date should be shown on event list for the current event.
* If date has changed from previous to current event it should be shown.
*
* @param event Current event where to show the date.
* @param prevEvent Previous event where to compare the date with.
* @return If date has changed and should be shown.
*/
protected showDate(event: AddonCalendarEventToDisplay, prevEvent?: AddonCalendarEventToDisplay): boolean {
if (!prevEvent) {
// First event, show it.
return true;
}
// Check if day has changed.
return !moment(event.timestart * 1000).isSame(prevEvent.timestart * 1000, 'day');
}
/**
* Check if event ends the same date or not.
*
* @param event Event info.
* @return If date has changed and should be shown.
*/
protected endsSameDay(event: AddonCalendarEventToDisplay): boolean {
if (!event.timeduration) {
// No duration.
return true;
}
// Check if day has changed.
return moment(event.timestart * 1000).isSame((event.timestart + event.timeduration) * 1000, 'day');
}
/**
* Show the context menu.
*
* @param event Event.
*/
async openFilter(event: MouseEvent): Promise<void> {
const popover = await this.popoverCtrl.create({
component: AddonCalendarFilterPopoverComponent,
componentProps: {
courses: this.courses,
filter: this.filter,
},
event,
});
await popover.present();
}
/**
* Open page to create/edit an event.
*
* @param eventId Event ID to edit.
*/
openEdit(eventId?: number): void {
this.eventId = undefined;
const params: Params = {};
if (eventId) {
params.eventId = eventId;
}
if (this.filter.courseId) {
params.courseId = this.filter.courseId;
}
CoreNavigator.instance.navigateToSitePath('/calendar/edit', { params }); // @todo , this.splitviewCtrl);
}
/**
* Open calendar events settings.
*/
openSettings(): void {
CoreNavigator.instance.navigateToSitePath('/calendar/settings');
}
/**
* Navigate to a particular event.
*
* @param eventId Event to load.
*/
gotoEvent(eventId: number): void {
this.eventId = eventId;
if (eventId < 0) {
// It's an offline event, go to the edit page.
this.openEdit(eventId);
} else {
/* this.splitviewCtrl.push('/calendar/event', {
id: eventId,
});*/
}
}
/**
* Find an event and mark it as deleted.
*
* @param eventId Event ID.
* @param deleted Whether to mark it as deleted or not.
*/
protected markAsDeleted(eventId: number, deleted: boolean): void {
const event = this.onlineEvents.find((event) => event.id == eventId);
if (event) {
event.deleted = deleted;
}
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.obsDefaultTimeChange?.off();
this.newEventObserver?.off();
this.discardedObserver?.off();
this.editEventObserver?.off();
this.deleteEventObserver?.off();
this.undeleteEventObserver?.off();
this.syncObserver?.off();
this.manualSyncObserver?.off();
this.filterChangedObserver?.off();
this.onlineObserver?.unsubscribe();
}
}

View File

@ -0,0 +1,5 @@
:host {
ion-note {
max-width: 30%;
}
}

View File

@ -0,0 +1,25 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>{{ 'core.settings.settings' | translate }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>
<ion-label>{{ 'addon.calendar.defaultnotificationtime' | translate }}</ion-label>
<ion-select [(ngModel)]="defaultTime" (ionChange)="updateDefaultTime($event)" interface="action-sheet">
<ion-select-option value="0">{{ 'core.settings.disabled' | translate }}</ion-select-option>
<ion-select-option value="10">{{ 600 | coreDuration }}</ion-select-option>
<ion-select-option value="30">{{ 1800 | coreDuration }}</ion-select-option>
<ion-select-option value="60">{{ 3600 | coreDuration }}</ion-select-option>
<ion-select-option value="120">{{ 7200 | coreDuration }}</ion-select-option>
<ion-select-option value="360">{{ 21600 | coreDuration }}</ion-select-option>
<ion-select-option value="720">{{ 43200 | coreDuration }}</ion-select-option>
<ion-select-option value="1440">{{ 86400 | coreDuration }}</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
</ion-content>

View File

@ -0,0 +1,50 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { AddonCalendarSettingsPage } from './settings';
const routes: Routes = [
{
path: '',
component: AddonCalendarSettingsPage,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CommonModule,
IonicModule,
FormsModule,
TranslateModule.forChild(),
CoreDirectivesModule,
CorePipesModule,
],
declarations: [
AddonCalendarSettingsPage,
],
exports: [RouterModule],
})
export class AddonCalendarSettingsPageModule {}

View File

@ -0,0 +1,53 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit } from '@angular/core';
import { AddonCalendar, AddonCalendarProvider } from '../../services/calendar';
import { CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites';
/**
* Page that displays the calendar settings.
*/
@Component({
selector: 'page-addon-calendar-settings',
templateUrl: 'settings.html',
})
export class AddonCalendarSettingsPage implements OnInit {
defaultTime = 0;
/**
* View loaded.
*/
async ngOnInit(): Promise<void> {
this.defaultTime = await AddonCalendar.instance.getDefaultNotificationTime();
}
/**
* Update default time.
*
* @param newTime New time.
*/
updateDefaultTime(newTime: number): void {
AddonCalendar.instance.setDefaultNotificationTime(newTime);
CoreEvents.trigger(
AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED,
{ time: newTime },
CoreSites.instance.getCurrentSiteId(),
);
}
}

View File

@ -0,0 +1,736 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreSites } from '@services/sites';
import {
AddonCalendar,
AddonCalendarDayName,
AddonCalendarEvent,
AddonCalendarEventBase,
AddonCalendarEventToDisplay,
AddonCalendarEventType,
AddonCalendarGetEventsEvent,
AddonCalendarProvider,
AddonCalendarWeek,
AddonCalendarWeekDay,
} from './calendar';
import { CoreConfig } from '@services/config';
import { CoreUtils } from '@services/utils/utils';
import { CoreCourse } from '@features/course/services/course';
import { ContextLevel, CoreConstants } from '@/core/constants';
import moment from 'moment';
import { makeSingleton } from '@singletons';
import { AddonCalendarSyncInvalidateEvent } from './calendar-sync';
import { AddonCalendarOfflineEventDBRecord } from './database/calendar-offline';
import { CoreCategoryData } from '@features/courses/services/courses';
/**
* Context levels enumeration.
*/
export enum AddonCalendarEventIcons {
SITE = 'fas-globe',
CATEGORY = 'fas-cubes',
COURSE = 'fas-graduation-cap',
GROUP = 'fas-users',
USER = 'fas-user',
}
/**
* Service that provides some features regarding lists of courses and categories.
*/
@Injectable({ providedIn: 'root' })
export class AddonCalendarHelperProvider {
protected eventTypeIcons: string[] = [];
/**
* Returns event icon based on event type.
*
* @param eventType Type of the event.
* @return Event icon.
*/
getEventIcon(eventType: AddonCalendarEventType): string {
if (this.eventTypeIcons.length == 0) {
CoreUtils.instance.enumKeys(AddonCalendarEventType).forEach((name) => {
const value = AddonCalendarEventType[name];
this.eventTypeIcons[value] = AddonCalendarEventIcons[name];
});
}
return this.eventTypeIcons[eventType] || '';
}
/**
* Calculate some day data based on a list of events for that day.
*
* @param day Day.
* @param events Events.
*/
calculateDayData(day: AddonCalendarWeekDay, events: AddonCalendarEventToDisplay[]): void {
day.hasevents = events.length > 0;
day.haslastdayofevent = false;
const types = {};
events.forEach((event) => {
types[event.formattedType || event.eventtype] = true;
if (event.islastday) {
day.haslastdayofevent = true;
}
});
day.calendareventtypes = Object.keys(types) as AddonCalendarEventType[];
}
/**
* Check if current user can create/edit events.
*
* @param courseId Course ID. If not defined, site calendar.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with boolean: whether the user can create events.
*/
async canEditEvents(courseId?: number, siteId?: string): Promise<boolean> {
try {
const canEdit = await AddonCalendar.instance.canEditEvents(siteId);
if (!canEdit) {
return false;
}
const types = await AddonCalendar.instance.getAllowedEventTypes(courseId, siteId);
return Object.keys(types).length > 0;
} catch {
return false;
}
}
/**
* Classify events into their respective months and days. If an event duration covers more than one day,
* it will be included in all the days it lasts.
*
* @param events Events to classify.
* @return Object with the classified events.
*/
classifyIntoMonths(
offlineEvents: AddonCalendarOfflineEventDBRecord[],
): { [monthId: string]: { [day: number]: AddonCalendarEventToDisplay[] } } {
// Format data.
const events: AddonCalendarEventToDisplay[] = offlineEvents.map((event) =>
AddonCalendarHelper.instance.formatOfflineEventData(event));
const result = {};
events.forEach((event) => {
const treatedDay = moment(new Date(event.timestart * 1000));
const endDay = moment(new Date((event.timestart + event.timeduration) * 1000));
// Add the event to all the days it lasts.
while (!treatedDay.isAfter(endDay, 'day')) {
const monthId = this.getMonthId(treatedDay.year(), treatedDay.month() + 1);
const day = treatedDay.date();
if (!result[monthId]) {
result[monthId] = {};
}
if (!result[monthId][day]) {
result[monthId][day] = [];
}
result[monthId][day].push(event);
treatedDay.add(1, 'day'); // Treat next day.
}
});
return result;
}
/**
* Convenience function to format some event data to be rendered.
*
* @param event Event to format.
*/
formatEventData(event: AddonCalendarEvent | AddonCalendarEventBase | AddonCalendarGetEventsEvent): AddonCalendarEventToDisplay {
const eventFormatted: AddonCalendarEventToDisplay = {
id: event.id!,
name: event.name,
eventtype: event.eventtype,
categoryid: event.categoryid,
groupid: event.groupid,
description: event.description,
location: 'location' in event? event.location : undefined,
timestart: event.timestart,
timeduration: event.timeduration,
eventcount: 'eventcount' in event? event.eventcount || 0 : 0,
repeatid: event.repeatid || 0,
// repeateditall: event.repeateditall,
userid: event.userid,
timemodified: event.timemodified,
eventIcon: this.getEventIcon(event.eventtype),
formattedType: AddonCalendar.instance.getEventType(event),
modulename: event.modulename,
format: 1,
visible: 1,
offline: false,
};
if (event.modulename) {
eventFormatted.eventIcon = CoreCourse.instance.getModuleIconSrc(event.modulename);
eventFormatted.moduleIcon = eventFormatted.eventIcon;
}
eventFormatted.formattedType = AddonCalendar.instance.getEventType(event);
// Calculate context.
if ('course' in event) {
eventFormatted.courseid = event.course?.id;
} else if ('courseid' in event) {
eventFormatted.courseid = event.courseid;
}
// Calculate context.
if ('category' in event) {
eventFormatted.categoryid = event.category?.id;
} else if ('categoryid' in event) {
eventFormatted.categoryid = event.categoryid;
}
if ('canedit' in event) {
eventFormatted.canedit = event.canedit;
}
if ('candelete' in event) {
eventFormatted.candelete = event.candelete;
}
this.formatEventContext(eventFormatted, eventFormatted.courseid, eventFormatted.categoryid);
return eventFormatted;
}
/**
* Convenience function to format some event data to be rendered.
*
* @param e Event to format.
*/
formatOfflineEventData(event: AddonCalendarOfflineEventDBRecord): AddonCalendarEventToDisplay {
const eventFormatted: AddonCalendarEventToDisplay = {
id: event.id!,
name: event.name,
timestart: event.timestart,
eventtype: event.eventtype,
categoryid: event.categoryid,
courseid: event.courseid || event.groupcourseid,
groupid: event.groupid,
description: event.description,
location: event.location,
duration: event.duration,
timedurationuntil: event.timedurationuntil,
timedurationminutes: event.timedurationminutes,
// repeat: event.repeat,
eventcount: event.repeats || 0,
repeatid: event.repeatid || 0,
// repeateditall: event.repeateditall,
userid: event.userid,
timemodified: event.timecreated || 0,
eventIcon: this.getEventIcon(event.eventtype),
formattedType: event.eventtype,
format: 1,
visible: 1,
offline: true,
timeduration: 0,
};
// Calculate context.
const categoryId = event.categoryid;
const courseId = event.courseid || event.groupcourseid;
this.formatEventContext(eventFormatted, courseId, categoryId);
if (eventFormatted.duration == 1) {
eventFormatted.timeduration = (event.timedurationuntil || 0) - event.timestart;
} else if (eventFormatted.duration == 2) {
eventFormatted.timeduration = (event.timedurationminutes || 0) * CoreConstants.SECONDS_MINUTE;
} else {
eventFormatted.timeduration = 0;
}
return eventFormatted;
}
/**
* Modifies event data with the context information.
*
* @param eventFormatted Event formatted to be displayed.
* @param courseId Course Id if any.
* @param categoryId Category Id if any.
*/
protected formatEventContext(eventFormatted: AddonCalendarEventToDisplay, courseId?: number, categoryId?: number): void {
if (categoryId && categoryId > 0) {
eventFormatted.contextLevel = ContextLevel.COURSECAT;
eventFormatted.contextInstanceId = categoryId;
} else if (courseId && courseId > 0) {
eventFormatted.contextLevel = ContextLevel.COURSE;
eventFormatted.contextInstanceId = courseId;
} else {
eventFormatted.contextLevel = ContextLevel.USER;
eventFormatted.contextInstanceId = eventFormatted.userid;
}
}
/**
* Get options (name & value) for each allowed event type.
*
* @param eventTypes Result of getAllowedEventTypes.
* @return Options.
*/
getEventTypeOptions(eventTypes: {[name: string]: boolean}): AddonCalendarEventTypeOption[] {
const options: AddonCalendarEventTypeOption[] = [];
if (eventTypes.user) {
options.push({ name: 'core.user', value: AddonCalendarEventType.USER });
}
if (eventTypes.group) {
options.push({ name: 'core.group', value: AddonCalendarEventType.GROUP });
}
if (eventTypes.course) {
options.push({ name: 'core.course', value: AddonCalendarEventType.COURSE });
}
if (eventTypes.category) {
options.push({ name: 'core.category', value: AddonCalendarEventType.CATEGORY });
}
if (eventTypes.site) {
options.push({ name: 'core.site', value: AddonCalendarEventType.SITE });
}
return options;
}
/**
* Get the month "id" (year + month).
*
* @param year Year.
* @param month Month.
* @return The "id".
*/
getMonthId(year: number, month: number): string {
return year + '#' + month;
}
/**
* Get weeks of a month in offline (with no events).
*
* The result has the same structure than getMonthlyEvents, but it only contains fields that are actually used by the app.
*
* @param year Year to get.
* @param month Month to get.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the response.
*/
async getOfflineMonthWeeks(
year: number,
month: number,
siteId?: string,
): Promise<{ daynames: Partial<AddonCalendarDayName>[]; weeks: Partial<AddonCalendarWeek>[] }> {
const site = await CoreSites.instance.getSite(siteId);
// Get starting week day user preference, fallback to site configuration.
let startWeekDayStr = site.getStoredConfig('calendar_startwday');
startWeekDayStr = await CoreConfig.instance.get(AddonCalendarProvider.STARTING_WEEK_DAY, startWeekDayStr);
const startWeekDay = parseInt(startWeekDayStr, 10);
const today = moment();
const isCurrentMonth = today.year() == year && today.month() == month - 1;
const weeks: Partial<AddonCalendarWeek>[] = [];
let date = moment({ year, month: month - 1, date: 1 });
for (let mday = 1; mday <= date.daysInMonth(); mday++) {
date = moment({ year, month: month - 1, date: mday });
// Add new week and calculate prepadding.
if (!weeks.length || date.day() == startWeekDay) {
const prepaddingLength = (date.day() - startWeekDay + 7) % 7;
const prepadding: number[] = [];
for (let i = 0; i < prepaddingLength; i++) {
prepadding.push(i);
}
weeks.push({ prepadding, postpadding: [], days: [] });
}
// Calculate postpadding of last week.
if (mday == date.daysInMonth()) {
const postpaddingLength = (startWeekDay - date.day() + 6) % 7;
const postpadding: number[] = [];
for (let i = 0; i < postpaddingLength; i++) {
postpadding.push(i);
}
weeks[weeks.length - 1].postpadding = postpadding;
}
// Add day to current week.
weeks[weeks.length - 1].days!.push({
events: [],
hasevents: false,
mday: date.date(),
isweekend: date.day() == 0 || date.day() == 6,
istoday: isCurrentMonth && today.date() == date.date(),
calendareventtypes: [],
// Added to match the type. And possibly unused.
popovertitle: '',
ispast: today.date() > date.date(),
seconds: date.seconds(),
minutes: date.minutes(),
hours: date.hours(),
wday: date.weekday(),
year: year,
yday: date.dayOfYear(),
timestamp: date.date(),
haslastdayofevent: false,
neweventtimestamp: 0,
previousperiod: 0, // Previousperiod.
nextperiod: 0, // Nextperiod.
navigation: '', // Navigation.
});
}
return { weeks, daynames: [{ dayno: startWeekDay }] };
}
/**
* Check if the data of an event has changed.
*
* @param data Current data.
* @param original Original data.
* @return True if data has changed, false otherwise.
*/
hasEventDataChanged(data: AddonCalendarOfflineEventDBRecord, original?: AddonCalendarOfflineEventDBRecord): boolean {
if (!original) {
// There is no original data, assume it hasn't changed.
return false;
}
// Check the fields that don't depend on any other.
if (data.name != original.name || data.timestart != original.timestart || data.eventtype != original.eventtype ||
data.description != original.description || data.location != original.location ||
data.duration != original.duration || data.repeat != original.repeat) {
return true;
}
// Check data that depends on eventtype.
if ((data.eventtype == AddonCalendarEventType.CATEGORY && data.categoryid != original.categoryid) ||
(data.eventtype == AddonCalendarEventType.COURSE && data.courseid != original.courseid) ||
(data.eventtype == AddonCalendarEventType.GROUP && data.groupcourseid != original.groupcourseid &&
data.groupid != original.groupid)) {
return true;
}
// Check data that depends on duration.
if ((data.duration == 1 && data.timedurationuntil != original.timedurationuntil) ||
(data.duration == 2 && data.timedurationminutes != original.timedurationminutes)) {
return true;
}
if (data.repeat && data.repeats != original.repeats) {
return true;
}
return false;
}
/**
* Filter events to be shown on the events list.
*
* @param events Events without filtering.
* @param filter Filter from popover.
* @param categories Categories indexed by ID.
* @return Filtered events.
*/
getFilteredEvents(
events: AddonCalendarEventToDisplay[],
filter: AddonCalendarFilter,
categories: { [id: number]: CoreCategoryData },
): AddonCalendarEventToDisplay[] {
// Do not filter.
if (!filter.filtered) {
return events;
}
const courseId = filter.courseId ? Number(filter.courseId) : undefined;
if (!courseId || courseId < 0) {
// Filter only by type.
return events.filter((event) => filter[event.formattedType]);
}
const categoryId = filter.categoryId ? Number(filter.categoryId) : undefined;
return events.filter((event) => filter[event.formattedType] &&
this.shouldDisplayEvent(event, categories, courseId, categoryId));
}
/**
* Check if an event should be displayed based on the filter.
*
* @param event Event object.
* @param courseId Course ID to filter.
* @param categoryId Category ID the course belongs to.
* @param categories Categories indexed by ID.
* @return Whether it should be displayed.
*/
protected shouldDisplayEvent(
event: AddonCalendarEventToDisplay,
categories: { [id: number]: CoreCategoryData },
courseId: number,
categoryId?: number,
): boolean {
if (event.eventtype == 'user' || event.eventtype == 'site') {
// User or site event, display it.
return true;
}
if (event.eventtype == 'category' && categories) {
if (!event.categoryid || !Object.keys(categories).length) {
// We can't tell if the course belongs to the category, display them all.
return true;
}
if (event.categoryid == categoryId) {
// The event is in the same category as the course, display it.
return true;
}
// Check parent categories.
let category = categories[categoryId!];
while (category) {
if (!category.parent) {
// Category doesn't have parent, stop.
break;
}
if (event.categoryid == category.parent) {
return true;
}
category = categories[category.parent];
}
return false;
}
const eventCourse = (event.course && event.course.id) || event.courseid;
// Show the event if it is from site home or if it matches the selected course.
return !!eventCourse && (eventCourse == CoreSites.instance.getCurrentSiteHomeId() || eventCourse == courseId);
}
/**
* Refresh the month & day for several created/edited/deleted events, and invalidate the months & days
* for their repeated events if needed.
*
* @param events Events that have been touched and number of times each event is repeated.
* @param siteId Site ID. If not defined, current site.
* @return Resolved when done.
*/
async refreshAfterChangeEvents(events: AddonCalendarSyncInvalidateEvent[], siteId?: string): Promise<void> {
const site = await CoreSites.instance.getSite(siteId);
const fetchTimestarts: number[] = [];
const invalidateTimestarts: number[] = [];
const promises: Promise<unknown>[] = [];
// Always fetch upcoming events.
promises.push(AddonCalendar.instance.getUpcomingEvents(undefined, undefined, true, site.id));
promises.concat(events.map(async (eventData) => {
if (eventData.repeated <= 1) {
// Not repeated.
fetchTimestarts.push(eventData.timestart);
return AddonCalendar.instance.invalidateEvent(eventData.id);
}
if (eventData.repeatid) {
// Being edited or deleted.
// We need to calculate the days to invalidate because the event date could have changed.
// We don't know if the repeated events are before or after this one, invalidate them all.
fetchTimestarts.push(eventData.timestart);
for (let i = 1; i < eventData.repeated; i++) {
invalidateTimestarts.push(eventData.timestart + CoreConstants.SECONDS_DAY * 7 * i);
invalidateTimestarts.push(eventData.timestart - CoreConstants.SECONDS_DAY * 7 * i);
}
// Get the repeated events to invalidate them.
const repeatedEvents =
await AddonCalendar.instance.getLocalEventsByRepeatIdFromLocalDb(eventData.repeatid, site.id);
await CoreUtils.instance.allPromises(repeatedEvents.map((event) =>
AddonCalendar.instance.invalidateEvent(event.id!)));
return;
}
// Being added.
let time = eventData.timestart;
fetchTimestarts.push(time);
while (eventData.repeated > 1) {
time += CoreConstants.SECONDS_DAY * 7;
eventData.repeated--;
invalidateTimestarts.push(time);
}
return;
}));
try {
await CoreUtils.instance.allPromisesIgnoringErrors(promises);
} finally {
const treatedMonths = {};
const treatedDays = {};
const finalPromises: Promise<unknown>[] =[AddonCalendar.instance.invalidateAllUpcomingEvents()];
// Fetch months and days.
fetchTimestarts.map((fetchTime) => {
const day = moment(new Date(fetchTime * 1000));
const monthId = this.getMonthId(day.year(), day.month() + 1);
if (!treatedMonths[monthId]) {
// Month not refetch or invalidated already, do it now.
treatedMonths[monthId] = true;
finalPromises.push(AddonCalendar.instance.getMonthlyEvents(
day.year(),
day.month() + 1,
undefined,
undefined,
true,
site.id,
));
}
const dayId = monthId + '#' + day.date();
if (!treatedDays[dayId]) {
// Dat not refetch or invalidated already, do it now.
treatedDays[dayId] = true;
finalPromises.push(AddonCalendar.instance.getDayEvents(
day.year(),
day.month() + 1,
day.date(),
undefined,
undefined,
true,
site.id,
));
}
});
// Invalidate months and days.
invalidateTimestarts.map((fetchTime) => {
const day = moment(new Date(fetchTime * 1000));
const monthId = this.getMonthId(day.year(), day.month() + 1);
if (!treatedMonths[monthId]) {
// Month not refetch or invalidated already, do it now.
treatedMonths[monthId] = true;
finalPromises.push(AddonCalendar.instance.invalidateMonthlyEvents(day.year(), day.month() + 1, site.id));
}
const dayId = monthId + '#' + day.date();
if (!treatedDays[dayId]) {
// Dat not refetch or invalidated already, do it now.
treatedDays[dayId] = true;
finalPromises.push(AddonCalendar.instance.invalidateDayEvents(
day.year(),
day.month() + 1,
day.date(),
site.id,
));
}
});
await CoreUtils.instance.allPromisesIgnoringErrors(finalPromises);
}
}
/**
* Refresh the month & day for a created/edited/deleted event, and invalidate the months & days
* for their repeated events if needed.
*
* @param event Event that has been touched.
* @param repeated Number of times the event is repeated.
* @param siteId Site ID. If not defined, current site.
* @return Resolved when done.
*/
refreshAfterChangeEvent(
event: {
id?: number;
repeatid?: number;
timestart: number;
},
repeated: number,
siteId?: string,
): Promise<void> {
return this.refreshAfterChangeEvents(
[{
id: event.id!,
repeatid: event.repeatid,
timestart: event.timestart,
repeated: repeated,
}],
siteId,
);
}
/**
* Sort events by timestart.
*
* @param events List to sort.
*/
sortEvents(events: (AddonCalendarEventToDisplay)[]): (AddonCalendarEventToDisplay)[] {
return events.sort((a, b) => {
if (a.timestart == b.timestart) {
return a.timeduration - b.timeduration;
}
return a.timestart - b.timestart;
});
}
}
export class AddonCalendarHelper extends makeSingleton(AddonCalendarHelperProvider) {}
/**
* Calculated data for Calendar filtering.
*/
export type AddonCalendarFilter = {
filtered: boolean; // If filter enabled (some filters applied).
courseId: number; // Course Id to filter.
categoryId?: number; // Category Id to filter.
course: boolean; // Filter to show course events.
group: boolean; // Filter to show group events.
site: boolean; // Filter to show show site events.
user: boolean; // Filter to show user events.
category: boolean; // Filter to show category events.
};
export type AddonCalendarEventTypeOption = {
name: string;
value: AddonCalendarEventType;
};

View File

@ -0,0 +1,276 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { AddonCalendarSubmitCreateUpdateFormDataWSParams } from './calendar';
import {
AddonCalendarOfflineDeletedEventDBRecord,
AddonCalendarOfflineEventDBRecord,
DELETED_EVENTS_TABLE,
EVENTS_TABLE,
} from './database/calendar-offline';
/**
* Service to handle offline calendar events.
*/
@Injectable({ providedIn: 'root' })
export class AddonCalendarOfflineProvider {
/**
* Delete an offline event.
*
* @param eventId Event ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if deleted, rejected if failure.
*/
async deleteEvent(eventId: number, siteId?: string): Promise<void> {
const site = await CoreSites.instance.getSite(siteId);
const conditions: SQLiteDBRecordValues = {
id: eventId,
};
await site.getDb().deleteRecords(EVENTS_TABLE, conditions);
}
/**
* Get the IDs of all the events created/edited/deleted in offline.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the IDs.
*/
async getAllEventsIds(siteId?: string): Promise<number[]> {
const promises: Promise<number[]>[] = [];
promises.push(this.getAllDeletedEventsIds(siteId));
promises.push(this.getAllEditedEventsIds(siteId));
const result = await Promise.all(promises);
return CoreUtils.instance.mergeArraysWithoutDuplicates(result[0], result[1]);
}
/**
* Get all the events deleted in offline.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with all the events deleted in offline.
*/
async getAllDeletedEvents(siteId?: string): Promise<AddonCalendarOfflineDeletedEventDBRecord[]> {
const site = await CoreSites.instance.getSite(siteId);
return await site.getDb().getRecords(DELETED_EVENTS_TABLE);
}
/**
* Get the IDs of all the events deleted in offline.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the IDs of all the events deleted in offline.
*/
async getAllDeletedEventsIds(siteId?: string): Promise<number[]> {
const events = await this.getAllDeletedEvents(siteId);
return events.map((event) => event.id);
}
/**
* Get all the events created/edited in offline.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with events.
*/
async getAllEditedEvents(siteId?: string): Promise<AddonCalendarOfflineEventDBRecord[]> {
const site = await CoreSites.instance.getSite(siteId);
return await site.getDb().getRecords(EVENTS_TABLE);
}
/**
* Get the IDs of all the events created/edited in offline.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with events IDs.
*/
async getAllEditedEventsIds(siteId?: string): Promise<number[]> {
const events = await this.getAllEditedEvents(siteId);
return events.map((event) => event.id!);
}
/**
* Get an event deleted in offline.
*
* @param eventId Event ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the deleted event.
*/
async getDeletedEvent(eventId: number, siteId?: string): Promise<AddonCalendarOfflineDeletedEventDBRecord> {
const site = await CoreSites.instance.getSite(siteId);
const conditions: SQLiteDBRecordValues = {
id: eventId,
};
return await site.getDb().getRecord(DELETED_EVENTS_TABLE, conditions);
}
/**
* Get an offline event.
*
* @param eventId Event ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the event.
*/
async getEvent(eventId: number, siteId?: string): Promise<AddonCalendarOfflineEventDBRecord> {
const site = await CoreSites.instance.getSite(siteId);
const conditions: SQLiteDBRecordValues = {
id: eventId,
};
return await site.getDb().getRecord(EVENTS_TABLE, conditions);
}
/**
* Check if there are offline events to send.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with boolean: true if has offline events, false otherwise.
*/
async hasEditedEvents(siteId?: string): Promise<boolean> {
try {
const events = await this.getAllEditedEvents(siteId);
return !!events.length;
} catch {
// No offline data found, return false.
return false;
}
}
/**
* Check whether there's offline data for a site.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with boolean: true if has offline data, false otherwise.
*/
async hasOfflineData(siteId?: string): Promise<boolean> {
const ids = await this.getAllEventsIds(siteId);
return ids.length > 0;
}
/**
* Check if an event is deleted.
*
* @param eventId Event ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with boolean: whether the event is deleted.
*/
async isEventDeleted(eventId: number, siteId?: string): Promise<boolean> {
try {
const event = await this.getDeletedEvent(eventId, siteId);
return !!event;
} catch {
return false;
}
}
/**
* Mark an event as deleted.
*
* @param eventId Event ID to delete.
* @param name Name of the event to delete.
* @param deleteAll If it's a repeated event. whether to delete all events of the series.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
async markDeleted(eventId: number, name: string, deleteAll?: boolean, siteId?: string): Promise<number> {
const site = await CoreSites.instance.getSite(siteId);
const event: AddonCalendarOfflineDeletedEventDBRecord = {
id: eventId,
name: name || '',
repeat: deleteAll ? 1 : 0,
timemodified: Date.now(),
};
return await site.getDb().insertRecord(DELETED_EVENTS_TABLE, event);
}
/**
* Offline version for adding a new discussion to a forum.
*
* @param eventId Event ID. If it's a new event, set it to undefined/null.
* @param data Event data.
* @param timeCreated The time the event was created. If not defined, current time.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the stored event.
*/
async saveEvent(
eventId: number | undefined,
data: AddonCalendarSubmitCreateUpdateFormDataWSParams,
timeCreated?: number,
siteId?: string,
): Promise<AddonCalendarOfflineEventDBRecord> {
const site = await CoreSites.instance.getSite(siteId);
timeCreated = timeCreated || Date.now();
const event: AddonCalendarOfflineEventDBRecord = {
id: eventId || -timeCreated,
name: data.name,
timestart: data.timestart,
eventtype: data.eventtype,
categoryid: data.categoryid,
courseid: data.courseid,
groupcourseid: data.groupcourseid,
groupid: data.groupid,
description: data.description && data.description.text,
location: data.location,
duration: data.duration,
timedurationuntil: data.timedurationuntil,
timedurationminutes: data.timedurationminutes,
repeat: data.repeat ? 1 : 0,
repeats: data.repeats,
repeatid: data.repeatid,
repeateditall: data.repeateditall ? 1 : 0,
timecreated: timeCreated,
userid: site.getUserId(),
};
await site.getDb().insertRecord(EVENTS_TABLE, event);
return event;
}
/**
* Unmark an event as deleted.
*
* @param eventId Event ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if deleted, rejected if failure.
*/
async unmarkDeleted(eventId: number, siteId?: string): Promise<void> {
const site = await CoreSites.instance.getSite(siteId);
const conditions: SQLiteDBRecordValues = {
id: eventId,
};
await site.getDb().deleteRecords(DELETED_EVENTS_TABLE, conditions);
}
}
export class AddonCalendarOffline extends makeSingleton(AddonCalendarOfflineProvider) {}

View File

@ -0,0 +1,322 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreApp } from '@services/app';
import { CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import {
AddonCalendar,
AddonCalendarEvent,
AddonCalendarProvider,
AddonCalendarSubmitCreateUpdateFormDataWSParams,
} from './calendar';
import { AddonCalendarOffline } from './calendar-offline';
import { AddonCalendarHelper } from './calendar-helper';
import { makeSingleton, Translate } from '@singletons';
import { CoreError } from '@classes/errors/error';
import { CoreSync } from '@services/sync';
import { CoreTextUtils } from '@services/utils/text';
/**
* Service to sync calendar.
*/
@Injectable({ providedIn: 'root' })
export class AddonCalendarSyncProvider extends CoreSyncBaseProvider<AddonCalendarSyncEvents> {
static readonly AUTO_SYNCED = 'addon_calendar_autom_synced';
static readonly MANUAL_SYNCED = 'addon_calendar_manual_synced';
static readonly SYNC_ID = 'calendar';
constructor() {
super('AddonCalendarSync');
}
/**
* Try to synchronize all events in a certain site or in all sites.
*
* @param siteId Site ID to sync. If not defined, sync all sites.
* @param force Wether to force sync not depending on last execution.
* @return Promise resolved if sync is successful, rejected if sync fails.
*/
async syncAllEvents(siteId?: string, force?: boolean): Promise<void> {
await this.syncOnSites('all calendar events', this.syncAllEventsFunc.bind(this, [force]), siteId);
}
/**
* Sync all events on a site.
*
* @param siteId Site ID to sync.
* @param force Wether to force sync not depending on last execution.
* @return Promise resolved if sync is successful, rejected if sync fails.
*/
protected async syncAllEventsFunc(siteId: string, force?: boolean): Promise<void> {
const result = await (force ? this.syncEvents(siteId) : this.syncEventsIfNeeded(siteId));
if (result && result.updated) {
// Sync successful, send event.
CoreEvents.trigger<AddonCalendarSyncEvents>(AddonCalendarSyncProvider.AUTO_SYNCED, result, siteId);
}
}
/**
* Sync a site events only if a certain time has passed since the last time.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the events are synced or if it doesn't need to be synced.
*/
async syncEventsIfNeeded(siteId?: string): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
const needed = await this.isSyncNeeded(AddonCalendarSyncProvider.SYNC_ID, siteId);
if (needed) {
await this.syncEvents(siteId);
}
}
/**
* Synchronize all offline events of a certain site.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if sync is successful, rejected otherwise.
*/
async syncEvents(siteId?: string): Promise<AddonCalendarSyncEvents> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
if (this.isSyncing(AddonCalendarSyncProvider.SYNC_ID, siteId)) {
// There's already a sync ongoing for this site, return the promise.
return this.getOngoingSync(AddonCalendarSyncProvider.SYNC_ID, siteId)!;
}
this.logger.debug('Try to sync calendar events for site ' + siteId);
// Get offline events.
const syncPromise = this.performSyncEvents(siteId);
return this.addOngoingSync(AddonCalendarSyncProvider.SYNC_ID, syncPromise, siteId);
}
/**
* Sync user preferences of a site.
*
* @param siteId Site ID to sync.
* @param Promise resolved if sync is successful, rejected if sync fails.
*/
protected async performSyncEvents(siteId: string): Promise<AddonCalendarSyncEvents> {
const result: AddonCalendarSyncEvents = {
warnings: [],
events: [],
deleted: [],
toinvalidate: [],
updated: false,
};
let eventIds: number[] = [];
try {
eventIds = await AddonCalendarOffline.instance.getAllEventsIds(siteId);
} catch {
// No offline data found.
}
if (eventIds.length > 0) {
if (!CoreApp.instance.isOnline()) {
// Cannot sync in offline.
throw new CoreError('Cannot sync while offline');
}
const promises = eventIds.map((eventId) => this.syncOfflineEvent(eventId, result, siteId));
await CoreUtils.instance.allPromises(promises);
if (result.updated) {
// Data has been sent to server. Now invalidate the WS calls.
const promises = [
AddonCalendar.instance.invalidateEventsList(siteId),
AddonCalendarHelper.instance.refreshAfterChangeEvents(result.toinvalidate, siteId),
];
await CoreUtils.instance.ignoreErrors(Promise.all(promises));
}
}
// Sync finished, set sync time.
await CoreUtils.instance.ignoreErrors(this.setSyncTime(AddonCalendarSyncProvider.SYNC_ID, siteId));
// All done, return the result.
return result;
}
/**
* Synchronize an offline event.
*
* @param eventId The event ID to sync.
* @param result Object where to store the result of the sync.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if sync is successful, rejected otherwise.
*/
protected async syncOfflineEvent(eventId: number, result: AddonCalendarSyncEvents, siteId?: string): Promise<void> {
// Verify that event isn't blocked.
if (CoreSync.instance.isBlocked(AddonCalendarProvider.COMPONENT, eventId, siteId)) {
this.logger.debug('Cannot sync event ' + eventId + ' because it is blocked.');
throw Translate.instance.instant(
'core.errorsyncblocked',
{ $a: Translate.instance.instant('addon.calendar.calendarevent') },
);
}
// First of all, check if the event has been deleted.
try {
const data = await AddonCalendarOffline.instance.getDeletedEvent(eventId, siteId);
// Delete the event.
try {
await AddonCalendar.instance.deleteEventOnline(data.id, !!data.repeat, siteId);
result.updated = true;
result.deleted.push(eventId);
// Event sent, delete the offline data.
const promises: Promise<void>[] = [];
promises.push(AddonCalendarOffline.instance.unmarkDeleted(eventId, siteId));
promises.push(AddonCalendarOffline.instance.deleteEvent(eventId, siteId).catch(() => {
// Ignore errors, maybe there was no edit data.
}));
// We need the event data to invalidate it. Get it from local DB.
promises.push(AddonCalendar.instance.getEventFromLocalDb(eventId, siteId).then((event) => {
result.toinvalidate.push({
id: event.id,
repeatid: event.repeatid,
timestart: event.timestart,
repeated: data?.repeat ? (event as AddonCalendarEvent).eventcount || 1 : 1,
});
return;
}).catch(() => {
// Ignore errors.
}));
await Promise.all(promises);
} catch (error) {
if (!CoreUtils.instance.isWebServiceError(error)) {
// Local error, reject.
throw error;
}
// The WebService has thrown an error, this means that the event cannot be created. Delete it.
result.updated = true;
const promises: Promise<void>[] = [];
promises.push(AddonCalendarOffline.instance.unmarkDeleted(eventId, siteId));
promises.push(AddonCalendarOffline.instance.deleteEvent(eventId, siteId).catch(() => {
// Ignore errors, maybe there was no edit data.
}));
await Promise.all(promises);
// Event deleted, add a warning.
result.warnings.push(Translate.instance.instant('core.warningofflinedatadeleted', {
component: Translate.instance.instant('addon.calendar.calendarevent'),
name: data.name,
error: CoreTextUtils.instance.getErrorMessageFromError(error),
}));
}
return;
} catch {
// Not deleted.
}
// Not deleted. Now get the event data.
const event = await AddonCalendarOffline.instance.getEvent(eventId, siteId);
// Try to send the data.
const data: AddonCalendarSubmitCreateUpdateFormDataWSParams = Object.assign(
CoreUtils.instance.clone(event),
{
description: {
text: event.description || '',
format: 1,
},
},
); // Clone the object because it will be modified in the submit function.
try {
const newEvent = await AddonCalendar.instance.submitEventOnline(eventId > 0 ? eventId : 0, data, siteId);
result.updated = true;
result.events.push(newEvent);
// Add data to invalidate.
const numberOfRepetitions = data.repeat ? data.repeats :
(data.repeateditall && newEvent.repeatid ? newEvent.eventcount : 1);
result.toinvalidate.push({
id: newEvent.id,
repeatid: newEvent.repeatid,
timestart: newEvent.timestart,
repeated: numberOfRepetitions || 1,
});
// Event sent, delete the offline data.
return AddonCalendarOffline.instance.deleteEvent(event.id!, siteId);
} catch (error) {
if (!CoreUtils.instance.isWebServiceError(error)) {
// Local error, reject.
throw error;
}
// The WebService has thrown an error, this means that the event cannot be created. Delete it.
result.updated = true;
await AddonCalendarOffline.instance.deleteEvent(event.id!, siteId);
// Event deleted, add a warning.
result.warnings.push(Translate.instance.instant('core.warningofflinedatadeleted', {
component: Translate.instance.instant('addon.calendar.calendarevent'),
name: event.name,
error: CoreTextUtils.instance.getErrorMessageFromError(error),
}));
}
}
}
export class AddonCalendarSync extends makeSingleton(AddonCalendarSyncProvider) {}
export type AddonCalendarSyncEvents = {
warnings: string[];
events: AddonCalendarEvent[];
deleted: number[];
toinvalidate: AddonCalendarSyncInvalidateEvent[];
updated: boolean;
source?: string; // Added on pages.
day?: number; // Added on day page.
month?: number; // Added on day page.
year?: number; // Added on day page.
};
export type AddonCalendarSyncInvalidateEvent = {
id: number;
repeatid?: number;
timestart: number;
repeated: number;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,164 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { CoreSiteSchema } from '@services/sites';
import { AddonCalendarEventType } from '../calendar';
/**
* Database variables for AddonDatabaseOffline service.
*/
export const EVENTS_TABLE = 'addon_calendar_offline_events';
export const DELETED_EVENTS_TABLE = 'addon_calendar_deleted_events';
export const CALENDAR_OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
name: 'AddonCalendarOfflineProvider',
version: 1,
tables: [
{
name: EVENTS_TABLE,
columns: [
{
name: 'id',
type: 'INTEGER',
primaryKey: true,
},
{
name: 'name',
type: 'TEXT',
notNull: true,
},
{
name: 'timestart',
type: 'INTEGER',
notNull: true,
},
{
name: 'eventtype',
type: 'TEXT',
notNull: true,
},
{
name: 'categoryid',
type: 'INTEGER',
},
{
name: 'courseid',
type: 'INTEGER',
},
{
name: 'groupcourseid',
type: 'INTEGER',
},
{
name: 'groupid',
type: 'INTEGER',
},
{
name: 'description',
type: 'TEXT',
},
{
name: 'location',
type: 'TEXT',
},
{
name: 'duration',
type: 'INTEGER',
},
{
name: 'timedurationuntil',
type: 'INTEGER',
},
{
name: 'timedurationminutes',
type: 'INTEGER',
},
{
name: 'repeat',
type: 'INTEGER',
},
{
name: 'repeats',
type: 'INTEGER',
},
{
name: 'repeatid',
type: 'INTEGER',
},
{
name: 'repeateditall',
type: 'INTEGER',
},
{
name: 'userid',
type: 'INTEGER',
},
{
name: 'timecreated',
type: 'INTEGER',
},
],
},
{
name: DELETED_EVENTS_TABLE,
columns: [
{
name: 'id',
type: 'INTEGER',
primaryKey: true,
},
{
name: 'name',
type: 'TEXT',
notNull: true,
},
{
name: 'repeat',
type: 'INTEGER',
},
{
name: 'timemodified',
type: 'INTEGER',
},
],
},
],
};
export type AddonCalendarOfflineEventDBRecord = {
id?: number; // Negative for offline entries.
name: string;
timestart: number;
eventtype: AddonCalendarEventType;
categoryid?: number;
courseid?: number;
groupcourseid?: number;
groupid?: number;
description?: string;
location?: string;
duration?: number;
timedurationuntil?: number;
timedurationminutes?: number;
repeat?: number;
repeats?: number;
repeatid?: number;
repeateditall?: number;
userid?: number;
timecreated?: number;
};
export type AddonCalendarOfflineDeletedEventDBRecord = {
id: number;
name: string; // Save the name to be able to notify the user.
repeat?: number;
timemodified?: number;
};

View File

@ -0,0 +1,276 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { SQLiteDB } from '@classes/sqlitedb';
import { CoreSiteSchema } from '@services/sites';
import { AddonCalendarEventType } from '../calendar';
/**
* Database variables for AddonDatabase service.
*/
export const EVENTS_TABLE = 'addon_calendar_events_3';
export const REMINDERS_TABLE = 'addon_calendar_reminders';
export const CALENDAR_SITE_SCHEMA: CoreSiteSchema = {
name: 'AddonCalendarProvider',
version: 3,
canBeCleared: [EVENTS_TABLE],
tables: [
{
name: EVENTS_TABLE,
columns: [
{
name: 'id',
type: 'INTEGER',
primaryKey: true,
},
{
name: 'name',
type: 'TEXT',
notNull: true,
},
{
name: 'description',
type: 'TEXT',
},
{
name: 'eventtype',
type: 'TEXT',
},
{
name: 'courseid',
type: 'INTEGER',
},
{
name: 'timestart',
type: 'INTEGER',
},
{
name: 'timeduration',
type: 'INTEGER',
},
{
name: 'categoryid',
type: 'INTEGER',
},
{
name: 'groupid',
type: 'INTEGER',
},
{
name: 'userid',
type: 'INTEGER',
},
{
name: 'instance',
type: 'INTEGER',
},
{
name: 'modulename',
type: 'TEXT',
},
{
name: 'timemodified',
type: 'INTEGER',
},
{
name: 'repeatid',
type: 'INTEGER',
},
{
name: 'visible',
type: 'INTEGER',
},
{
name: 'uuid',
type: 'TEXT',
},
{
name: 'sequence',
type: 'INTEGER',
},
{
name: 'subscriptionid',
type: 'INTEGER',
},
{
name: 'location',
type: 'TEXT',
},
{
name: 'eventcount',
type: 'INTEGER',
},
{
name: 'timesort',
type: 'INTEGER',
},
{
name: 'category',
type: 'TEXT',
},
{
name: 'course',
type: 'TEXT',
},
{
name: 'subscription',
type: 'TEXT',
},
{
name: 'canedit',
type: 'INTEGER',
},
{
name: 'candelete',
type: 'INTEGER',
},
{
name: 'deleteurl',
type: 'TEXT',
},
{
name: 'editurl',
type: 'TEXT',
},
{
name: 'viewurl',
type: 'TEXT',
},
{
name: 'isactionevent',
type: 'INTEGER',
},
{
name: 'url',
type: 'TEXT',
},
{
name: 'islastday',
type: 'INTEGER',
},
{
name: 'popupname',
type: 'TEXT',
},
{
name: 'mindaytimestamp',
type: 'INTEGER',
},
{
name: 'maxdaytimestamp',
type: 'INTEGER',
},
{
name: 'draggable',
type: 'INTEGER',
},
],
},
{
name: REMINDERS_TABLE,
columns: [
{
name: 'id',
type: 'INTEGER',
primaryKey: true,
},
{
name: 'eventid',
type: 'INTEGER',
},
{
name: 'time',
type: 'INTEGER',
},
],
uniqueKeys: [
['eventid', 'time'],
],
},
],
async migrate(db: SQLiteDB, oldVersion: number): Promise<void> {
if (oldVersion < 3) {
const newTable = EVENTS_TABLE;
let oldTable = 'addon_calendar_events_2';
try {
await db.tableExists(oldTable);
} catch {
// The v2 table doesn't exist, try with v1.
oldTable = 'addon_calendar_events';
}
await db.tableExists(oldTable);
// Move the records from the old table.
const events = await db.getAllRecords<AddonCalendarEventDBRecord>(oldTable);
const promises = events.map((event) => db.insertRecord(newTable, event));
await Promise.all(promises);
try {
db.dropTable(oldTable);
} catch {
// Old table does not exist, ignore.
}
}
},
};
export type AddonCalendarEventDBRecord = {
id?: number;
name: string;
description: string;
eventtype: AddonCalendarEventType;
timestart: number;
timeduration: number;
categoryid?: number;
groupid?: number;
userid?: number;
instance?: number;
modulename?: string;
timemodified: number;
repeatid?: number;
visible: number;
// Following properties are only available on AddonCalendarGetEventsEvent
courseid?: number;
uuid?: string;
sequence?: number;
subscriptionid?: number;
// Following properties are only available on AddonCalendarCalendarEvent
location?: string;
eventcount?: number;
timesort?: number;
category?: string;
course?: string;
subscription?: string;
canedit?: number;
candelete?: number;
deleteurl?: string;
editurl?: string;
viewurl?: string;
isactionevent?: number;
url?: string;
islastday?: number;
popupname?: string;
mindaytimestamp?: number;
maxdaytimestamp?: number;
draggable?: number;
};
export type AddonCalendarReminderDBRecord = {
id?: number;
eventid: number;
time: number;
};

View File

@ -0,0 +1,57 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { AddonCalendar } from '../calendar';
import { makeSingleton } from '@singletons';
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
/**
* Handler to inject an option into main menu.
*/
@Injectable({ providedIn: 'root' })
export class AddonCalendarMainMenuHandlerService implements CoreMainMenuHandler {
static readonly PAGE_NAME = 'calendar';
name = 'AddonCalendar';
priority = 900;
/**
* Check if the handler is enabled on a site level.
*
* @return Whether or not the handler is enabled on a site level.
*/
async isEnabled(): Promise<boolean> {
return !AddonCalendar.instance.isCalendarDisabledInSite();
}
/**
* Returns the data needed to render the handler.
*
* @return Data needed to render the handler.
*/
getDisplayData(): CoreMainMenuHandlerData {
return {
icon: 'far-calendar',
title: 'addon.calendar.calendar',
page: AddonCalendar.instance.getMainCalendarPagePath(),
class: 'addon-calendar-handler',
};
}
}
export class AddonCalendarMainMenuHandler extends makeSingleton(AddonCalendarMainMenuHandlerService) {}

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreCronHandler } from '@services/cron';
import { makeSingleton } from '@singletons';
import { AddonCalendarSync } from '../calendar-sync';
/**
* Synchronization cron handler.
*/
@Injectable({ providedIn: 'root' })
export class AddonCalendarSyncCronHandlerService implements CoreCronHandler {
name = 'AddonCalendarSyncCronHandler';
/**
* Execute the process.
* Receives the ID of the site affected, undefined for all sites.
*
* @param siteId ID of the site affected, undefined for all sites.
* @param force Wether the execution is forced (manual sync).
* @return Promise resolved when done, rejected if failure.
*/
async execute(siteId?: string, force?: boolean): Promise<void> {
await AddonCalendarSync.instance.syncAllEvents(siteId, force);
}
/**
* Get the time between consecutive executions.
*
* @return Time between consecutive executions (in ms).
*/
getInterval(): number {
return AddonCalendarSync.instance.syncInterval;
}
}
export class AddonCalendarSyncCronHandler extends makeSingleton(AddonCalendarSyncCronHandlerService) {}

View File

@ -0,0 +1,114 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Params } from '@angular/router';
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons';
import { AddonCalendar } from '../calendar';
const SUPPORTED_VIEWS = ['month', 'mini', 'minithree', 'day', 'upcoming', 'upcoming_mini'];
/**
* Content links handler for calendar view page.
*/
@Injectable({ providedIn: 'root' })
export class AddonCalendarViewLinkHandlerService extends CoreContentLinksHandlerBase {
name = 'AddonCalendarViewLinkHandler';
pattern = /\/calendar\/view\.php/;
/**
* Get the list of actions for a link (url).
*
* @param siteIds List of sites the URL belongs to.
* @param url The URL to treat.
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @return List of (or promise resolved with list of) actions.
*/
getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
return [{
action: (siteId?: string): void => {
if (!params.view || params.view == 'month' || params.view == 'mini' || params.view == 'minithree') {
// Monthly view, open the calendar tab.
const stateParams: Params = {
courseId: params.course,
};
const timestamp = params.time ? params.time * 1000 : Date.now();
const date = new Date(timestamp);
stateParams.year = date.getFullYear();
stateParams.month = date.getMonth() + 1;
// @todo: Add checkMenu param.
CoreNavigator.instance.navigateToSitePath('/calendar/index', { params: stateParams, siteId });
} else if (params.view == 'day') {
// Daily view, open the page.
const stateParams: Params = {
courseId: params.course,
};
const timestamp = params.time ? params.time * 1000 : Date.now();
const date = new Date(timestamp);
stateParams.year = date.getFullYear();
stateParams.month = date.getMonth() + 1;
stateParams.day = date.getDate();
CoreNavigator.instance.navigateToSitePath('/calendar/day', { params: stateParams, siteId });
} else if (params.view == 'upcoming' || params.view == 'upcoming_mini') {
// Upcoming view, open the calendar tab.
const stateParams: Params = {
courseId: params.course,
upcoming: true,
};
// @todo: Add checkMenu param.
CoreNavigator.instance.navigateToSitePath('/calendar/index', { params: stateParams, siteId });
}
},
}];
}
/**
* Check if the handler is enabled for a certain site (site + user) and a URL.
* If not defined, defaults to true.
*
* @param siteId The site ID.
* @param url The URL to treat.
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @return Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: Params): boolean | Promise<boolean> {
if (params.view && SUPPORTED_VIEWS.indexOf(params.view) == -1) {
// This type of view isn't supported in the app.
return false;
}
return AddonCalendar.instance.isDisabled(siteId).then((disabled) => {
if (disabled) {
return false;
}
return AddonCalendar.instance.canViewMonth(siteId);
});
}
}
export class AddonCalendarViewLinkHandler extends makeSingleton(AddonCalendarViewLinkHandlerService) {}

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="14.0054" y1="0" x2="14.0054" y2="20.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="16.3,0 15.4,0 6.7,0 6,0 6,20 6.7,20 21.7,20 22,20 22,6.6 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="14.0054" y1="1" x2="14.0054" y2="19.0005">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="7,19 7,1 15.8,1 21,6.9 21,19 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="14.0054" y1="2" x2="14.0054" y2="18.0005">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="8,18 8,2 15.4,2 20,7.3 20,18 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="18.3101" y1="0" x2="18.3101" y2="7.7852">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M14.8,7.5c0,0,5.2-1.3,7.2,0.3c0-0.1,0-1.2,0-1.2L16.2,0c0,0-1.5,0-1.6,0
C16.8,3,14.8,7.5,14.8,7.5z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="16.3003" y1="6.1616" x2="18.5911" y2="3.8708">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="0.5181" style="stop-color:#E5F3FC"/>
<stop offset="0.7045" style="stop-color:#DEF0FB"/>
<stop offset="0.8371" style="stop-color:#D3EBFA"/>
<stop offset="0.872" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BDD8F0"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.87" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.872" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BDD8F0"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M16.3,6.2c0.3-1.2,0.5-2.9,0.1-4.4l4,4.4C20,6.1,19.4,6,18.8,6C17.9,6,17,6.1,16.3,6.2z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="11.9673" y1="12.167" x2="11.9673" y2="23.8853">
<stop offset="0" style="stop-color:#DDA976"/>
<stop offset="1" style="stop-color:#9F6B37"/>
<a:midPointStop offset="0" style="stop-color:#DDA976"/>
<a:midPointStop offset="0.5" style="stop-color:#DDA976"/>
<a:midPointStop offset="1" style="stop-color:#9F6B37"/>
</linearGradient>
<path style="fill:url(#SVGID_6_);" d="M10,18.9c-0.3-2.2,9.4,0.5,9.3-1.4c0-0.8-8.4-3.4-11.4-4.1c-0.9-0.2-6.5-1.2-6.5-1.2
c-0.2,0.1-1.3,3.3-1.4,5.2c0.5,0.3,7.3,6.7,10,6.5c2.7-0.2,14.2-3.6,13.9-4.7C23.3,17.1,10.3,21.3,10,18.9z"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="11.4893" y1="13.2803" x2="11.4893" y2="22.9336">
<stop offset="0" style="stop-color:#FFDDAA"/>
<stop offset="1" style="stop-color:#E3B17E"/>
<a:midPointStop offset="0" style="stop-color:#FFDDAA"/>
<a:midPointStop offset="0.5" style="stop-color:#FFDDAA"/>
<a:midPointStop offset="1" style="stop-color:#E3B17E"/>
</linearGradient>
<path style="fill:url(#SVGID_7_);" d="M1.7,17.4C1.4,17.2,1.2,17,1,16.9c0.1-1.2,0.6-2.8,0.9-3.6c1.8,0.3,5.1,0.9,5.7,1
c2.6,0.6,9.4,2.3,9.7,3c0.5,1.1-8.3-1.9-8.1,1.7c0.2,3.5,12.4-0.6,12.7,0.5c0.2,0.7-10.1,3.6-12,3.5C7.9,22.8,3.3,18.8,1.7,17.4z"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="7.5928" y1="14.4141" x2="7.5928" y2="21.9336">
<stop offset="0" style="stop-color:#F1C592"/>
<stop offset="1" style="stop-color:#E1AF7C"/>
<a:midPointStop offset="0" style="stop-color:#F1C592"/>
<a:midPointStop offset="0.5" style="stop-color:#F1C592"/>
<a:midPointStop offset="1" style="stop-color:#E1AF7C"/>
</linearGradient>
<path style="fill:url(#SVGID_8_);" d="M10.1,21.9c-0.8-0.1-2.8-1-7.6-5.2l-0.3-0.3c0.1-0.7,0.3-1.4,0.5-2.1c2.1,0.4,4.3,0.8,4.7,0.9
c1.5,0.4,2.7,0.7,3.8,0.9c-1,0.1-1.8,0.4-2.3,1c-0.3,0.3-0.7,0.9-0.6,1.9c0.1,1.1,0.8,2.4,4,2.4c0.3,0,0.6,0,0.9,0
C11.8,21.7,10.7,21.9,10.1,21.9L10.1,21.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="14.0054" y1="0" x2="14.0054" y2="20.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="16.3,0 15.4,0 6.7,0 6,0 6,20 6.7,20 21.7,20 22,20 22,6.6 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="14.0054" y1="1" x2="14.0054" y2="19.0005">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="7,19 7,1 15.8,1 21,6.9 21,19 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="14.0054" y1="2" x2="14.0054" y2="18.0005">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="8,18 8,2 15.4,2 20,7.3 20,18 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="18.3101" y1="0" x2="18.3101" y2="7.7852">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M14.8,7.5c0,0,5.2-1.3,7.2,0.3c0-0.1,0-1.2,0-1.2L16.2,0c0,0-1.5,0-1.6,0
C16.8,3,14.8,7.5,14.8,7.5z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="16.3003" y1="6.1616" x2="18.5911" y2="3.8708">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="0.5181" style="stop-color:#E5F3FC"/>
<stop offset="0.7045" style="stop-color:#DEF0FB"/>
<stop offset="0.8371" style="stop-color:#D3EBFA"/>
<stop offset="0.872" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BDD8F0"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.87" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.872" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BDD8F0"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M16.3,6.2c0.3-1.2,0.5-2.9,0.1-4.4l4,4.4C20,6.1,19.4,6,18.8,6C17.9,6,17,6.1,16.3,6.2z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="11.9673" y1="12.167" x2="11.9673" y2="23.8853">
<stop offset="0" style="stop-color:#DDA976"/>
<stop offset="1" style="stop-color:#9F6B37"/>
<a:midPointStop offset="0" style="stop-color:#DDA976"/>
<a:midPointStop offset="0.5" style="stop-color:#DDA976"/>
<a:midPointStop offset="1" style="stop-color:#9F6B37"/>
</linearGradient>
<path style="fill:url(#SVGID_6_);" d="M10,18.9c-0.3-2.2,9.4,0.5,9.3-1.4c0-0.8-8.4-3.4-11.4-4.1c-0.9-0.2-6.5-1.2-6.5-1.2
c-0.2,0.1-1.3,3.3-1.4,5.2c0.5,0.3,7.3,6.7,10,6.5c2.7-0.2,14.2-3.6,13.9-4.7C23.3,17.1,10.3,21.3,10,18.9z"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="11.4893" y1="13.2803" x2="11.4893" y2="22.9336">
<stop offset="0" style="stop-color:#FFDDAA"/>
<stop offset="1" style="stop-color:#E3B17E"/>
<a:midPointStop offset="0" style="stop-color:#FFDDAA"/>
<a:midPointStop offset="0.5" style="stop-color:#FFDDAA"/>
<a:midPointStop offset="1" style="stop-color:#E3B17E"/>
</linearGradient>
<path style="fill:url(#SVGID_7_);" d="M1.7,17.4C1.4,17.2,1.2,17,1,16.9c0.1-1.2,0.6-2.8,0.9-3.6c1.8,0.3,5.1,0.9,5.7,1
c2.6,0.6,9.4,2.3,9.7,3c0.5,1.1-8.3-1.9-8.1,1.7c0.2,3.5,12.4-0.6,12.7,0.5c0.2,0.7-10.1,3.6-12,3.5C7.9,22.8,3.3,18.8,1.7,17.4z"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="7.5928" y1="14.4141" x2="7.5928" y2="21.9336">
<stop offset="0" style="stop-color:#F1C592"/>
<stop offset="1" style="stop-color:#E1AF7C"/>
<a:midPointStop offset="0" style="stop-color:#F1C592"/>
<a:midPointStop offset="0.5" style="stop-color:#F1C592"/>
<a:midPointStop offset="1" style="stop-color:#E1AF7C"/>
</linearGradient>
<path style="fill:url(#SVGID_8_);" d="M10.1,21.9c-0.8-0.1-2.8-1-7.6-5.2l-0.3-0.3c0.1-0.7,0.3-1.4,0.5-2.1c2.1,0.4,4.3,0.8,4.7,0.9
c1.5,0.4,2.7,0.7,3.8,0.9c-1,0.1-1.8,0.4-2.3,1c-0.3,0.3-0.7,0.9-0.6,1.9c0.1,1.1,0.8,2.4,4,2.4c0.3,0,0.6,0,0.9,0
C11.8,21.7,10.7,21.9,10.1,21.9L10.1,21.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-2 0 24 24" style="overflow:visible;enable-background:new -2 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="12.0005" y1="0" x2="12.0005" y2="24.0005">
<stop offset="0" style="stop-color:#90C50E"/>
<stop offset="1" style="stop-color:#70A034"/>
<a:midPointStop offset="0" style="stop-color:#90C50E"/>
<a:midPointStop offset="0.5" style="stop-color:#90C50E"/>
<a:midPointStop offset="1" style="stop-color:#70A034"/>
</linearGradient>
<rect x="4" style="fill:url(#SVGID_1_);" width="16" height="24"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="12.0005" y1="1" x2="12.0005" y2="23.0005">
<stop offset="0" style="stop-color:#D9F991"/>
<stop offset="0.2388" style="stop-color:#D7F88D"/>
<stop offset="0.4501" style="stop-color:#D1F383"/>
<stop offset="0.6509" style="stop-color:#C6EC71"/>
<stop offset="0.844" style="stop-color:#B7E257"/>
<stop offset="1" style="stop-color:#A8D73D"/>
<a:midPointStop offset="0" style="stop-color:#D9F991"/>
<a:midPointStop offset="0.7317" style="stop-color:#D9F991"/>
<a:midPointStop offset="1" style="stop-color:#A8D73D"/>
</linearGradient>
<rect x="5" y="1" style="fill:url(#SVGID_2_);" width="14" height="22"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="12.0005" y1="2" x2="12.0005" y2="22.0005">
<stop offset="0" style="stop-color:#B3E810"/>
<stop offset="1" style="stop-color:#90C60D"/>
<a:midPointStop offset="0" style="stop-color:#B3E810"/>
<a:midPointStop offset="0.5" style="stop-color:#B3E810"/>
<a:midPointStop offset="1" style="stop-color:#90C60D"/>
</linearGradient>
<rect x="6" y="2" style="fill:url(#SVGID_3_);" width="12" height="20"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="12.0005" y1="4" x2="12.0005" y2="9">
<stop offset="0" style="stop-color:#90C50E"/>
<stop offset="1" style="stop-color:#70A034"/>
<a:midPointStop offset="0" style="stop-color:#90C50E"/>
<a:midPointStop offset="0.5" style="stop-color:#90C50E"/>
<a:midPointStop offset="1" style="stop-color:#70A034"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M7,4v5h10V4H7z M16,8H8V5h8V8z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="2.5" y1="0" x2="2.5" y2="24.0005">
<stop offset="0" style="stop-color:#656565"/>
<stop offset="1.342887e-02" style="stop-color:#646464"/>
<stop offset="0.4453" style="stop-color:#3C3C3C"/>
<stop offset="0.7891" style="stop-color:#242424"/>
<stop offset="1" style="stop-color:#1B1B1B"/>
<a:midPointStop offset="0" style="stop-color:#656565"/>
<a:midPointStop offset="0.4" style="stop-color:#656565"/>
<a:midPointStop offset="1" style="stop-color:#1B1B1B"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M4,0H2.6H1C0.5,0,0,0.5,0,1v22c0,0.5,0.5,1,1,1h1.6H4h1v-1V1V0H4z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="2.5" y1="1" x2="2.5" y2="23.0005">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="1" y="1" style="fill:url(#SVGID_6_);" width="3" height="22"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="2.5" y1="2" x2="2.5" y2="22.0005">
<stop offset="0" style="stop-color:#7C7C7C"/>
<stop offset="0.3898" style="stop-color:#5C5C5C"/>
<stop offset="0.768" style="stop-color:#444444"/>
<stop offset="1" style="stop-color:#3B3B3B"/>
<a:midPointStop offset="0" style="stop-color:#7C7C7C"/>
<a:midPointStop offset="0.4" style="stop-color:#7C7C7C"/>
<a:midPointStop offset="1" style="stop-color:#3B3B3B"/>
</linearGradient>
<rect x="2" y="2" style="fill:url(#SVGID_7_);" width="1" height="20"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-0.1 -0.1 24 24"
style="overflow:visible;enable-background:new -0.1 -0.1 24 24;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="10.9429" y1="0" x2="10.9429" y2="19.7881">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M10.8,0C4.7,0.1-0.1,4.3,0,9.3c0,2.6,1.5,5,3.7,6.6c0,0.1,0,0.2,0.1,0.3
c0.3,2-0.9,3.6-0.9,3.6s2.2-0.5,3.5-1.5c0.2-0.2,0.5-0.4,0.7-0.7c1.3,0.4,2.6,0.6,4.1,0.6c6-0.1,10.9-4.3,10.8-9.3
C21.8,3.9,16.8-0.1,10.8,0z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="10.9434" y1="1" x2="10.9434" y2="18.1289">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M4.6,18.1c0.1-0.6,0.2-1.3,0.1-2.1c0-0.1,0-0.2-0.1-0.3l-0.1-0.4l-0.3-0.3
C2.2,13.6,1,11.5,1,9.3C0.9,4.8,5.3,1.1,10.8,1L11,1c5.4,0,9.8,3.5,9.9,7.9c0.1,4.5-4.3,8.2-9.8,8.3l-0.2,0c-1.2,0-2.4-0.2-3.5-0.5
l-0.6-0.2l-0.4,0.5c-0.2,0.2-0.4,0.4-0.5,0.6C5.4,17.7,5,17.9,4.6,18.1z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="10.9434" y1="2" x2="10.9434" y2="16.1631">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M10.8,16.2c-1.1,0-2.2-0.2-3.2-0.5l-1.3-0.4L5.7,16c0,0,0-0.1,0-0.1c0-0.1,0-0.3-0.1-0.4
l-0.1-0.7l-0.6-0.6l0,0C3,13,2,11.2,2,9.3C1.9,5.3,5.9,2.1,10.8,2L11,2c4.8,0,8.8,3.1,8.8,6.9c0.1,3.9-3.9,7.2-8.8,7.2L10.8,16.2z"
/>
<path style="fill:#FFFFFF;" d="M16.6,9.6c0,0.7-0.6,1.3-1.2,1.3c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.2,1.3-1.2
C16,8.4,16.6,8.9,16.6,9.6z M11.5,8.4c-0.7,0-1.2,0.6-1.2,1.2c0,0.7,0.6,1.3,1.2,1.3c0.7,0,1.3-0.6,1.3-1.3
C12.7,8.9,12.2,8.4,11.5,8.4z M7.5,8.4c-0.7,0-1.3,0.6-1.3,1.2c0,0.7,0.6,1.3,1.3,1.3s1.3-0.6,1.3-1.3C8.8,8.9,8.2,8.4,7.5,8.4z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="14.0254" y1="6.667" x2="14.0254" y2="23.8398">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M20.7,20.5c1.9-1.4,3.2-3.4,3.2-5.7c0.1-4.4-4.3-8-9.8-8.1C8.7,6.6,4.2,10,4.1,14.4
c-0.1,4.4,4.3,8,9.8,8.1c1.4,0,2.7-0.2,3.9-0.5c1,1.3,3.7,1.9,3.7,1.9S20.3,22.2,20.7,20.5z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="14.1436" y1="7.667" x2="14.1436" y2="22.1943">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M19.9,22.2c-0.5-0.2-1-0.5-1.7-1.4c-0.4,0.1-1.7,0.7-4.2,0.7c-5,0-8.9-3.3-8.8-7.1
c0.1-3.7,4-6.8,9-6.8c2.3,0,4.7,0.8,6.3,2.2c1.6,1.3,2.5,3.1,2.4,4.9c0,1.9-1,3.7-3.1,5.1C19.7,20.7,19.7,21.6,19.9,22.2z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="14.0234" y1="8.6689" x2="14.0234" y2="20.5176">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<path style="fill:url(#SVGID_6_);" d="M13.9,20.5c-6.1-0.2-7.9-4.2-7.8-6.1c0.2-3.2,3.3-5.8,8-5.8c2.1,0,4.3,0.8,5.7,2
c1.4,1.1,2.1,2.6,2.1,4.1c-0.1,2.9-2.9,4.5-2.9,4.5s-0.2,0.7-0.2,0.9l-0.4-0.5C18.4,19.6,16.5,20.6,13.9,20.5z"/>
<path style="fill:#FFFFFF;" d="M19.4,14.9c0,0.7-0.6,1.3-1.2,1.3c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.2,1.3-1.2
C18.8,13.7,19.4,14.2,19.4,14.9z M14.3,13.7c-0.7,0-1.2,0.6-1.2,1.2c0,0.7,0.6,1.3,1.2,1.3c0.7,0,1.3-0.6,1.3-1.3
C15.5,14.2,15,13.7,14.3,13.7z M10.3,13.7c-0.7,0-1.3,0.6-1.3,1.2c0,0.7,0.6,1.3,1.3,1.3s1.3-0.6,1.3-1.3
C11.6,14.2,11,13.7,10.3,13.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-4.2 -0.3 24 24"
style="overflow:visible;enable-background:new -4.2 -0.3 24 24;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="7.8237" y1="0" x2="7.8237" y2="23.3262">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M2.4,8.4C1.1,8.4,0,7.5,0,6.1C0,3.3,2.8,0,7.7,0c3.3,0,7.9,2.2,7.9,6c0,2-1.2,3.5-3.6,4.4
c-3.3,1.2-1.4,3.7-4.5,3.7c-1.3,0-2.1-0.8-2.1-2.1c0-2.7,2.8-4.2,2.8-6.9c0-0.7-0.3-1.6-1.1-1.6c-0.9,0-1,0.9-1,1.5
C5.8,7.1,4.5,8.4,2.4,8.4z M7.3,23.3c-2,0-3.6-1.6-3.6-3.6c0-2,1.6-3.6,3.6-3.6c2,0,3.6,1.6,3.6,3.6C10.8,21.7,9.2,23.3,7.3,23.3z"
/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="7.8237" y1="1" x2="7.8237" y2="22.3262">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M7.3,22.3c-1.4,0-2.6-1.1-2.6-2.6c0-1.4,1.1-2.6,2.6-2.6s2.6,1.1,2.6,2.6
C9.8,21.2,8.7,22.3,7.3,22.3z M7.6,13.1c-0.8,0-1.1-0.4-1.1-1.1c0-1.1,0.6-1.9,1.2-2.9c0.7-1.1,1.5-2.3,1.5-4c0-1.3-0.7-2.6-2.1-2.6
c-1.8,0-2,1.7-2,2.4C4.9,6,4.3,7.4,2.4,7.4C1.6,7.4,1,6.9,1,6.1C1,4,3.1,1,7.7,1c2.9,0,6.9,1.9,6.9,5c0,1.6-1,2.7-2.9,3.4
c-2,0.8-2.5,2-2.9,2.8C8.5,13,8.5,13.1,7.6,13.1z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="7.8237" y1="2.0986" x2="7.8237" y2="21.3262">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M7.3,21.3c-0.9,0-1.6-0.7-1.6-1.6s0.7-1.6,1.6-1.6c0.9,0,1.6,0.7,1.6,1.6S8.2,21.3,7.3,21.3z
M7.6,12.1c-0.1,0-0.1,0-0.1,0c0,0,0-0.1,0-0.1c0-0.8,0.5-1.5,1-2.3c0.8-1.2,1.7-2.6,1.7-4.6c0-1.3-0.5-2.3-1.3-3
c2.2,0.4,4.8,1.8,4.8,3.9c0,0.4,0,1.6-2.3,2.5C9,9.4,8.3,11,7.9,11.9c0,0.1-0.1,0.2-0.1,0.2C7.8,12.1,7.7,12.1,7.6,12.1z M2.4,6.4
C2,6.4,2,6.2,2,6.1c0-1.1,0.9-2.7,2.7-3.5C4.4,3.1,4.1,3.8,4,4.8C3.9,6.2,3.1,6.4,2.4,6.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-2 -1 24 24" style="overflow:visible;enable-background:new -2 -1 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<radialGradient id="SVGID_1_" cx="10" cy="19.5" r="7.4917" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</radialGradient>
<path style="fill:url(#SVGID_1_);" d="M20,21c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2v-3c0-1.1,0.9-2,2-2h16c1.1,0,2,0.9,2,2V21z"/>
<radialGradient id="SVGID_2_" cx="10" cy="19.5" r="6.6049" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</radialGradient>
<path style="fill:url(#SVGID_2_);" d="M2,22c-0.6,0-1-0.4-1-1v-3c0-0.6,0.4-1,1-1h16c0.6,0,1,0.4,1,1v3c0,0.6-0.4,1-1,1H2z"/>
<radialGradient id="SVGID_3_" cx="10" cy="19.5" r="5.7554" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</radialGradient>
<rect x="2" y="18" style="fill:url(#SVGID_3_);" width="16" height="3"/>
<radialGradient id="SVGID_4_" cx="10" cy="11.5" r="7.4917" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</radialGradient>
<path style="fill:url(#SVGID_4_);" d="M20,13c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2v-3c0-1.1,0.9-2,2-2h16c1.1,0,2,0.9,2,2V13z"/>
<radialGradient id="SVGID_5_" cx="10" cy="11.5" r="6.6049" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</radialGradient>
<path style="fill:url(#SVGID_5_);" d="M2,14c-0.6,0-1-0.4-1-1v-3c0-0.6,0.4-1,1-1h16c0.6,0,1,0.4,1,1v3c0,0.6-0.4,1-1,1H2z"/>
<radialGradient id="SVGID_6_" cx="10" cy="11.5" r="5.7554" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</radialGradient>
<rect x="2" y="10" style="fill:url(#SVGID_6_);" width="16" height="3"/>
<radialGradient id="SVGID_7_" cx="10" cy="3.5" r="7.4917" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</radialGradient>
<path style="fill:url(#SVGID_7_);" d="M20,5c0,1.1-0.9,2-2,2H2C0.9,7,0,6.1,0,5V2c0-1.1,0.9-2,2-2h16c1.1,0,2,0.9,2,2V5z"/>
<radialGradient id="SVGID_8_" cx="10" cy="3.5" r="6.6049" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</radialGradient>
<path style="fill:url(#SVGID_8_);" d="M2,6C1.4,6,1,5.6,1,5V2c0-0.6,0.4-1,1-1h16c0.6,0,1,0.4,1,1v3c0,0.6-0.4,1-1,1H2z"/>
<radialGradient id="SVGID_9_" cx="10" cy="3.5" r="5.7554" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</radialGradient>
<rect x="2" y="2" style="fill:url(#SVGID_9_);" width="16" height="3"/>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-2 -1 24 24" style="overflow:visible;enable-background:new -2 -1 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<radialGradient id="SVGID_1_" cx="10" cy="19.5" r="7.4917" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</radialGradient>
<path style="fill:url(#SVGID_1_);" d="M20,21c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2v-3c0-1.1,0.9-2,2-2h16c1.1,0,2,0.9,2,2V21z"/>
<radialGradient id="SVGID_2_" cx="10" cy="19.5" r="6.6049" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</radialGradient>
<path style="fill:url(#SVGID_2_);" d="M2,22c-0.6,0-1-0.4-1-1v-3c0-0.6,0.4-1,1-1h16c0.6,0,1,0.4,1,1v3c0,0.6-0.4,1-1,1H2z"/>
<radialGradient id="SVGID_3_" cx="10" cy="19.5" r="5.7554" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</radialGradient>
<rect x="2" y="18" style="fill:url(#SVGID_3_);" width="16" height="3"/>
<radialGradient id="SVGID_4_" cx="10" cy="11.5" r="7.4917" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</radialGradient>
<path style="fill:url(#SVGID_4_);" d="M20,13c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2v-3c0-1.1,0.9-2,2-2h16c1.1,0,2,0.9,2,2V13z"/>
<radialGradient id="SVGID_5_" cx="10" cy="11.5" r="6.6049" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</radialGradient>
<path style="fill:url(#SVGID_5_);" d="M2,14c-0.6,0-1-0.4-1-1v-3c0-0.6,0.4-1,1-1h16c0.6,0,1,0.4,1,1v3c0,0.6-0.4,1-1,1H2z"/>
<radialGradient id="SVGID_6_" cx="10" cy="11.5" r="5.7554" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</radialGradient>
<rect x="2" y="10" style="fill:url(#SVGID_6_);" width="16" height="3"/>
<radialGradient id="SVGID_7_" cx="10" cy="3.5" r="7.4917" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</radialGradient>
<path style="fill:url(#SVGID_7_);" d="M20,5c0,1.1-0.9,2-2,2H2C0.9,7,0,6.1,0,5V2c0-1.1,0.9-2,2-2h16c1.1,0,2,0.9,2,2V5z"/>
<radialGradient id="SVGID_8_" cx="10" cy="3.5" r="6.6049" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</radialGradient>
<path style="fill:url(#SVGID_8_);" d="M2,6C1.4,6,1,5.6,1,5V2c0-0.6,0.4-1,1-1h16c0.6,0,1,0.4,1,1v3c0,0.6-0.4,1-1,1H2z"/>
<radialGradient id="SVGID_9_" cx="10" cy="3.5" r="5.7554" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</radialGradient>
<rect x="2" y="2" style="fill:url(#SVGID_9_);" width="16" height="3"/>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="24.001">
<stop offset="0" style="stop-color:#90C50E"/>
<stop offset="1" style="stop-color:#70A034"/>
<a:midPointStop offset="0" style="stop-color:#90C50E"/>
<a:midPointStop offset="0.5" style="stop-color:#90C50E"/>
<a:midPointStop offset="1" style="stop-color:#70A034"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M21.1,12.3c1,0,1.7,0.9,1.7,0.9c0.3,0.4,0.7,0.8,0.9,0.8s0.3-0.5,0.3-1V8c0-0.5-0.5-1-1-1h-3
c-0.5,0-1-0.1-1-0.3c0-0.2,0.3-0.7,0.7-1.1c0,0,0.8-0.9,0.8-2.1C20.5,1.6,18.9,0,17,0s-3.5,1.6-3.5,3.5c0,1.2,0.8,2.1,0.8,2.1
C14.7,6,15,6.5,15,6.7C15,6.9,14.5,7,14,7H8C7.5,7,7,7.5,7,8v6c0,0.5-0.1,1-0.3,1S6,14.7,5.6,14.3c0,0-0.9-0.8-2.1-0.8
C1.6,13.5,0,15.1,0,17s1.6,3.5,3.5,3.5c1.2,0,2.1-0.8,2.1-0.8C6,19.3,6.5,19,6.7,19S7,19.5,7,20v3c0,0.5,0.5,1,1,1h4
c0.5,0,1-0.1,1-0.3s-0.3-0.6-0.7-1c0,0-0.6-0.6-0.6-1.6c0-1.4,1.3-2.5,2.7-2.5c1.4,0,2.4,1.1,2.4,2.5c0,1-0.9,1.7-0.9,1.7
c-0.4,0.3-0.8,0.7-0.8,0.9s0.5,0.3,1,0.3h7c0.5,0,1-0.5,1-1v-6c0-0.5-0.1-1-0.3-1s-0.6,0.3-1,0.7c0,0-0.6,0.6-1.6,0.6
c-1.4,0-2.5-1.3-2.5-2.7S19.7,12.3,21.1,12.3z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="1" x2="11.9995" y2="23.001">
<stop offset="0" style="stop-color:#D9F991"/>
<stop offset="0.2388" style="stop-color:#D7F88D"/>
<stop offset="0.4501" style="stop-color:#D1F383"/>
<stop offset="0.6509" style="stop-color:#C6EC71"/>
<stop offset="0.844" style="stop-color:#B7E257"/>
<stop offset="1" style="stop-color:#A8D73D"/>
<a:midPointStop offset="0" style="stop-color:#D9F991"/>
<a:midPointStop offset="0.7317" style="stop-color:#D9F991"/>
<a:midPointStop offset="1" style="stop-color:#A8D73D"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M17,23c0.4-0.4,0.7-1.1,0.7-1.9c0-2-1.5-3.5-3.4-3.5c-2,0-3.7,1.6-3.7,3.5
c0,0.9,0.3,1.5,0.6,1.9H8v-3c0-1.5-0.7-2-1.3-2c-0.6,0-1.3,0.6-1.7,0.9c0,0-0.7,0.6-1.5,0.6C2.1,19.5,1,18.4,1,17s1.1-2.5,2.5-2.5
c0.8,0,1.4,0.6,1.5,0.6C5.3,15.4,6.1,16,6.7,16C7.3,16,8,15.5,8,14V8h6c1.5,0,2-0.7,2-1.3c0-0.6-0.5-1.3-0.9-1.7
c0,0-0.6-0.7-0.6-1.5C14.5,2.1,15.6,1,17,1s2.5,1.1,2.5,2.5c0,0.8-0.6,1.5-0.6,1.5C18.5,5.4,18,6.1,18,6.7C18,7.3,18.5,8,20,8h3v4
c-0.4-0.4-1.1-0.7-1.9-0.7c-2,0-3.5,1.5-3.5,3.4c0,2,1.6,3.7,3.5,3.7c0.9,0,1.5-0.3,1.9-0.6V23H17z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="2" x2="11.9995" y2="22.001">
<stop offset="0" style="stop-color:#B3E810"/>
<stop offset="1" style="stop-color:#90C60D"/>
<a:midPointStop offset="0" style="stop-color:#B3E810"/>
<a:midPointStop offset="0.5" style="stop-color:#B3E810"/>
<a:midPointStop offset="1" style="stop-color:#90C60D"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M18.6,22c0.1-0.3,0.1-0.6,0.1-0.9c0-2.5-1.9-4.5-4.4-4.5c-2.5,0-4.7,2.1-4.7,4.5
c0,0.3,0,0.6,0.1,0.9H9v-2c0-2.1-1.2-3-2.3-3C7.8,17,9,16.1,9,14V9h5c2.1,0,3-1.2,3-2.3c0-0.7-0.4-1.5-1.2-2.4
c-0.1-0.1-0.3-0.5-0.3-0.8C15.5,2.7,16.2,2,17,2s1.5,0.7,1.5,1.5c0,0.3-0.3,0.7-0.3,0.8C17.4,5.2,17,6,17,6.7C17,7.8,17.9,9,20,9h2
v1.4c-0.3-0.1-0.6-0.1-0.9-0.1c-2.5,0-4.5,1.9-4.5,4.4c0,2.5,2.1,4.7,4.5,4.7c0.3,0,0.6,0,0.9-0.1V22H18.6z M3.5,18.5
C2.7,18.5,2,17.8,2,17s0.7-1.5,1.5-1.5c0.3,0,0.7,0.3,0.8,0.3C5.2,16.6,6,17,6.7,17c-0.7,0-1.5,0.4-2.4,1.2
C4.2,18.3,3.8,18.5,3.5,18.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="7.7681" y1="10.2231" x2="7.7681" y2="23.9805">
<stop offset="0" style="stop-color:#DB6D17"/>
<stop offset="1" style="stop-color:#BF3B08"/>
<a:midPointStop offset="0" style="stop-color:#DB6D17"/>
<a:midPointStop offset="0.5" style="stop-color:#DB6D17"/>
<a:midPointStop offset="1" style="stop-color:#BF3B08"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M10.9,19.1c-1.1-0.8-1.7-1.1-2-2.4c-0.2-1.1-1.3-6.4-1.3-6.4l-4.9,1c0,0,0.9,4.6,1.8,9
c0.9,4.4,1,4.3,6.8,3.1C13.7,22.8,12.8,20.5,10.9,19.1z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="7.8569" y1="11.3994" x2="7.8569" y2="22.9805">
<stop offset="0" style="stop-color:#F6A55E"/>
<stop offset="1" style="stop-color:#EA5B03"/>
<a:midPointStop offset="0" style="stop-color:#F6A55E"/>
<a:midPointStop offset="0.5" style="stop-color:#F6A55E"/>
<a:midPointStop offset="1" style="stop-color:#EA5B03"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M7,23c-0.8,0-0.9,0-1.5-3l-1.6-8l2.9-0.6L8,16.9c0.3,1.6,1,2.1,1.9,2.7l0.4,0.3
c1.1,0.8,1.5,1.7,1.4,2.1c0,0.2-0.5,0.3-0.6,0.3l-0.5,0.1C9,22.7,7.8,23,7,23L7,23z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="7.7373" y1="12.5757" x2="7.7373" y2="21.8926">
<stop offset="0" style="stop-color:#F17219"/>
<stop offset="1" style="stop-color:#EA5B03"/>
<a:midPointStop offset="0" style="stop-color:#F17219"/>
<a:midPointStop offset="0.5" style="stop-color:#F17219"/>
<a:midPointStop offset="1" style="stop-color:#EA5B03"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M7.7,21.9c-0.7,0-0.7,0-1.2-2.1l-1.4-7l1-0.2L7,17.1c0.4,1.9,1.4,2.7,2.3,3.3l0.4,0.3
C10.8,21.6,10.6,21.4,7.7,21.9z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="11.0005" y1="0" x2="11.0005" y2="19.0039">
<stop offset="0" style="stop-color:#C3C3C3"/>
<stop offset="1" style="stop-color:#ACACAC"/>
<a:midPointStop offset="0" style="stop-color:#C3C3C3"/>
<a:midPointStop offset="0.5" style="stop-color:#C3C3C3"/>
<a:midPointStop offset="1" style="stop-color:#ACACAC"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M12.5,5H4.6C2.1,5,0,7.2,0,9.5C0,11.8,2.1,14,4.6,14h7.6c0,0,5.7,0,9.8,5c0-4.9,0-9.5,0-9.5
s0-4.3,0-9.5C18.7,4.8,12.5,5,12.5,5z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="11.0005" y1="2.6079" x2="11.0005" y2="16.5342">
<stop offset="0" style="stop-color:#E9E9E9"/>
<stop offset="1" style="stop-color:#C4C4C4"/>
<a:midPointStop offset="0" style="stop-color:#E9E9E9"/>
<a:midPointStop offset="0.5" style="stop-color:#E9E9E9"/>
<a:midPointStop offset="1" style="stop-color:#C4C4C4"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M21,16.5C17,13,12.5,13,12.2,13H4.6C2.7,13,1,11.4,1,9.5S2.7,6,4.6,6h7.8
c0.3,0,4.9-0.2,8.5-3.4V16.5z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="11.0005" y1="4.5991" x2="11.0005" y2="14.5273">
<stop offset="0" style="stop-color:#F7F7F7"/>
<stop offset="0.1044" style="stop-color:#FCFCFC"/>
<stop offset="0.3293" style="stop-color:#FFFFFF"/>
<stop offset="0.5692" style="stop-color:#E8E8E8"/>
<stop offset="0.8153" style="stop-color:#D7D7D7"/>
<stop offset="1" style="stop-color:#D1D1D1"/>
<a:midPointStop offset="0" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.2222" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.3293" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0.3545" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="1" style="stop-color:#D1D1D1"/>
</linearGradient>
<path style="fill:url(#SVGID_6_);" d="M20,14.5C16.2,12,12.4,12,12.2,12H4.6C3.3,12,2,10.8,2,9.5S3.3,7,4.6,7h7.8
c0.2,0,4-0.2,7.5-2.4V14.5z"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="5.501" y1="5.0015" x2="5.501" y2="14.002">
<stop offset="0" style="stop-color:#DB6D17"/>
<stop offset="1" style="stop-color:#BF3B08"/>
<a:midPointStop offset="0" style="stop-color:#DB6D17"/>
<a:midPointStop offset="0.5" style="stop-color:#DB6D17"/>
<a:midPointStop offset="1" style="stop-color:#BF3B08"/>
</linearGradient>
<path style="fill:url(#SVGID_7_);" d="M11,5H4.6C2.1,5,0,7.2,0,9.5C0,11.8,2.1,14,4.6,14H11V5z"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="6.001" y1="6.0015" x2="6.001" y2="13.0015">
<stop offset="0" style="stop-color:#F6A55E"/>
<stop offset="1" style="stop-color:#EA5B03"/>
<a:midPointStop offset="0" style="stop-color:#F6A55E"/>
<a:midPointStop offset="0.5" style="stop-color:#F6A55E"/>
<a:midPointStop offset="1" style="stop-color:#EA5B03"/>
</linearGradient>
<path style="fill:url(#SVGID_8_);" d="M11,6H4.6C2.7,6,1,7.6,1,9.5S2.7,13,4.6,13H11V6z"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="6.501" y1="7.0015" x2="6.501" y2="12.0015">
<stop offset="0.2195" style="stop-color:#F6A55E"/>
<stop offset="0.5076" style="stop-color:#F28C3F"/>
<stop offset="1" style="stop-color:#EA5B03"/>
<a:midPointStop offset="0.2195" style="stop-color:#F6A55E"/>
<a:midPointStop offset="0.5304" style="stop-color:#F6A55E"/>
<a:midPointStop offset="1" style="stop-color:#EA5B03"/>
</linearGradient>
<path style="fill:url(#SVGID_9_);" d="M11,7H4.6C3.3,7,2,8.2,2,9.5S3.3,12,4.6,12H11V7z"/>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="22.002" y1="1.464844e-03" x2="22.002" y2="19.002">
<stop offset="0" style="stop-color:#DB6D17"/>
<stop offset="1" style="stop-color:#BF3B08"/>
<a:midPointStop offset="0" style="stop-color:#DB6D17"/>
<a:midPointStop offset="0.5" style="stop-color:#DB6D17"/>
<a:midPointStop offset="1" style="stop-color:#BF3B08"/>
</linearGradient>
<path style="fill:url(#SVGID_10_);" d="M20,17c0,1.1,0.9,2,2,2l0,0c1.1,0,2-0.9,2-2V2c0-1.1-0.9-2-2-2l0,0c-1.1,0-2,0.9-2,2V17z"/>
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="22.002" y1="1.0015" x2="22.002" y2="18.002">
<stop offset="0" style="stop-color:#F6A55E"/>
<stop offset="1" style="stop-color:#EA5B03"/>
<a:midPointStop offset="0" style="stop-color:#F6A55E"/>
<a:midPointStop offset="0.5" style="stop-color:#F6A55E"/>
<a:midPointStop offset="1" style="stop-color:#EA5B03"/>
</linearGradient>
<path style="fill:url(#SVGID_11_);" d="M22,18c-0.6,0-1-0.4-1-1V2c0-0.6,0.4-1,1-1s1,0.4,1,1v15C23,17.6,22.6,18,22,18z"/>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="22.002" y1="1.0015" x2="22.002" y2="18.002">
<stop offset="0" style="stop-color:#F17219"/>
<stop offset="9.009037e-02" style="stop-color:#F38A39"/>
<stop offset="0.183" style="stop-color:#F59E54"/>
<stop offset="0.2378" style="stop-color:#F6A55E"/>
<stop offset="0.2464" style="stop-color:#F5A35C"/>
<stop offset="0.3809" style="stop-color:#EC8740"/>
<stop offset="0.5155" style="stop-color:#E5722C"/>
<stop offset="0.649" style="stop-color:#E06620"/>
<stop offset="0.7805" style="stop-color:#DF621C"/>
<stop offset="1" style="stop-color:#D64701"/>
<a:midPointStop offset="0" style="stop-color:#F17219"/>
<a:midPointStop offset="0.4103" style="stop-color:#F17219"/>
<a:midPointStop offset="0.2378" style="stop-color:#F6A55E"/>
<a:midPointStop offset="0.296" style="stop-color:#F6A55E"/>
<a:midPointStop offset="0.7805" style="stop-color:#DF621C"/>
<a:midPointStop offset="0.5" style="stop-color:#DF621C"/>
<a:midPointStop offset="1" style="stop-color:#D64701"/>
</linearGradient>
<path style="fill:url(#SVGID_12_);" d="M22,18c-0.6,0-1-0.4-1-1V2c0-0.6,0.4-1,1-1s1,0.4,1,1v15C23,17.6,22.6,18,22,18z"/>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-3 0 24 24" style="overflow:visible;enable-background:new -3 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="9.4995" y1="0" x2="9.4995" y2="24.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="11.5,0 0,0 0,24 19,24 19,7.9 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="9.4995" y1="1" x2="9.4995" y2="23.0005">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="1,23 1,1 11.1,1 18,8.3 18,23 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="9.4995" y1="2" x2="9.4995" y2="22.0005">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="2,22 2,2 10.6,2 17,8.7 17,22 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="14.2451" y1="0" x2="14.2451" y2="9.3594">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M10,9c0,0,5.2-1.5,9,0.4c0-0.1,0-1.5,0-1.5L11.5,0c0,0-1.8,0-2,0C12.1,3.7,10,9,10,9z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="11.3223" y1="7.5449" x2="14.4504" y2="4.4168">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="0.5181" style="stop-color:#E5F3FC"/>
<stop offset="0.7045" style="stop-color:#DEF0FB"/>
<stop offset="0.8371" style="stop-color:#D3EBFA"/>
<stop offset="0.872" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BDD8F0"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.87" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.872" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BDD8F0"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M17.5,7.8c-0.9-0.2-2-0.3-3.1-0.3c-1.1,0-2.1,0.1-3,0.3c0.4-1.6,0.7-4.1-0.2-6.4L17.5,7.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-0.1 -2 24 24"
style="overflow:visible;enable-background:new -0.1 -2 24 24;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="11.9351" y1="0" x2="11.9351" y2="20.0005">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M21.9,19c0,0.5-0.5,1-1,1h-18c-0.5,0-1-0.5-1-1V1.1c0-0.5,0.5-1.1,1-1.1h4
c0.5,0,1.4,0.3,1.8,0.6l0.9,0.7C10.1,1.7,10.9,2,11.4,2l9.5,0c0.5,0,1,0.5,1,1V19z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="11.9351" y1="0.9888" x2="11.9351" y2="19.0005">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M2.9,19V1.1C2.9,1.1,3,1,3,1l3.9,0c0.3,0,0.9,0.2,1.2,0.4L9,2.2C9.7,2.6,10.7,3,11.4,3l9.5,0
v16H2.9z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="11.9351" y1="1.9917" x2="11.9351" y2="18.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M3.9,18V2l3,0C7,2,7.4,2.1,7.5,2.2L8.4,3c0.8,0.6,2.1,1,3,1l8.5,0v14H3.9z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="11.936" y1="5" x2="11.936" y2="20.0005">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M23,19c0,0.5-0.5,1-1.1,1h-20c-0.5,0-1-0.5-1.1-1L0,6c0-0.5,0.4-1,0.9-1h22c0.5,0,1,0.5,0.9,1
L23,19z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="11.9673" y1="5.9541" x2="11.9673" y2="19.0005">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M1.9,19c0,0-0.1-0.1-0.1-0.1L1,6l21.9,0L22,18.9c0,0,0,0.1-0.1,0.1H1.9z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="11.9663" y1="6.9565" x2="11.9663" y2="18.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<polygon style="fill:url(#SVGID_6_);" points="2.8,18 2.1,7 21.9,7 21.1,18 "/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="10.4995" y1="0" x2="10.4995" y2="19.4312">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M19,0H2C0.9,0,0,0.9,0,2v11.3C0,14.4,0.9,15,2,15h0v4.4L6.1,15H19c1.1,0,2-0.5,2-1.6V2
C21,0.9,20,0,19,0z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="10.4995" y1="0.9531" x2="10.4995" y2="16.8384">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M3,14H2c-0.4,0-1-0.1-1-0.6V2c0-0.6,0.5-1,1-1H19c0.5,0,1,0.5,1,1v11.3c0,0.6-0.8,0.6-1,0.6
H5.7L3,16.8V14z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="10.4985" y1="1.9775" x2="10.4985" y2="14.314">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M4,13c0,0-2,0-2,0V2l17,0l0,11c0,0-13.8,0-13.8,0L4,14.3V13z"/>
<path style="fill:#FFFFFF;" d="M17,11H4v-1h13V11z M17,8H4v1h13V8z M17,6H4v1h13V6z M17,4H4v1h13V4z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="15" y1="7.8955" x2="15" y2="23.897">
<stop offset="0" style="stop-color:#90C50E"/>
<stop offset="1" style="stop-color:#70A034"/>
<a:midPointStop offset="0" style="stop-color:#90C50E"/>
<a:midPointStop offset="0.5" style="stop-color:#90C50E"/>
<a:midPointStop offset="1" style="stop-color:#70A034"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M22,7.9H8c-1.1,0-2,0.7-2,1.8v9c0,1.1,0.9,2,2,2v3.2l3.1-3H22c1.1,0,2-1.1,2-2.2v-9
C24,8.6,23.1,7.9,22,7.9z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="15" y1="8.8955" x2="15" y2="21.3911">
<stop offset="0" style="stop-color:#D9F991"/>
<stop offset="0.2388" style="stop-color:#D7F88D"/>
<stop offset="0.4501" style="stop-color:#D1F383"/>
<stop offset="0.6509" style="stop-color:#C6EC71"/>
<stop offset="0.844" style="stop-color:#B7E257"/>
<stop offset="1" style="stop-color:#A8D73D"/>
<a:midPointStop offset="0" style="stop-color:#D9F991"/>
<a:midPointStop offset="0.7317" style="stop-color:#D9F991"/>
<a:midPointStop offset="1" style="stop-color:#A8D73D"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M9,19.9H8c-0.6,0-1-0.7-1-1.2v-9c0-0.6,0.4-0.8,1-0.8h14c0.6,0,1,0.2,1,0.8v9
c0,0.6-0.4,1.2-1,1.2H10.6L9,21.4V19.9z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="15" y1="9.8955" x2="15" y2="18.896">
<stop offset="0" style="stop-color:#B3E810"/>
<stop offset="1" style="stop-color:#90C60D"/>
<a:midPointStop offset="0" style="stop-color:#B3E810"/>
<a:midPointStop offset="0.5" style="stop-color:#B3E810"/>
<a:midPointStop offset="1" style="stop-color:#90C60D"/>
</linearGradient>
<polygon style="fill:url(#SVGID_6_);" points="10,18.9 8,18.9 8,9.9 22,9.9 22,18.9 10.2,18.9 10,18.9 "/>
<path style="fill:#FFFFFF;" d="M20,16.9H10v-1h10V16.9z M20,13.9H10v1h10V13.9z M20,11.9H10v1h10V11.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-2 0 24 24" style="overflow:visible;enable-background:new -2 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="17.5" y1="16" x2="17.5" y2="22">
<stop offset="0" style="stop-color:#DB6D17"/>
<stop offset="1" style="stop-color:#BF3B08"/>
<a:midPointStop offset="0" style="stop-color:#DB6D17"/>
<a:midPointStop offset="0.5" style="stop-color:#DB6D17"/>
<a:midPointStop offset="1" style="stop-color:#BF3B08"/>
</linearGradient>
<rect x="15" y="16" style="fill:url(#SVGID_1_);" width="5" height="6"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="17.5" y1="17" x2="17.5" y2="21">
<stop offset="0" style="stop-color:#F6A55E"/>
<stop offset="1" style="stop-color:#EA5B03"/>
<a:midPointStop offset="0" style="stop-color:#F6A55E"/>
<a:midPointStop offset="0.5" style="stop-color:#F6A55E"/>
<a:midPointStop offset="1" style="stop-color:#EA5B03"/>
</linearGradient>
<rect x="16" y="17" style="fill:url(#SVGID_2_);" width="3" height="4"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="17.5" y1="18" x2="17.5" y2="20">
<stop offset="0" style="stop-color:#F17219"/>
<stop offset="1" style="stop-color:#EA5B03"/>
<a:midPointStop offset="0" style="stop-color:#F17219"/>
<a:midPointStop offset="0.5" style="stop-color:#F17219"/>
<a:midPointStop offset="1" style="stop-color:#EA5B03"/>
</linearGradient>
<rect x="17" y="18" style="fill:url(#SVGID_3_);" width="1" height="2"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="17.5" y1="9" x2="17.5" y2="15.0005">
<stop offset="0" style="stop-color:#90C50E"/>
<stop offset="1" style="stop-color:#70A034"/>
<a:midPointStop offset="0" style="stop-color:#90C50E"/>
<a:midPointStop offset="0.5" style="stop-color:#90C50E"/>
<a:midPointStop offset="1" style="stop-color:#70A034"/>
</linearGradient>
<rect x="15" y="9" style="fill:url(#SVGID_4_);" width="5" height="6"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="17.5" y1="10" x2="17.5" y2="14.0005">
<stop offset="0" style="stop-color:#D9F991"/>
<stop offset="0.2388" style="stop-color:#D7F88D"/>
<stop offset="0.4501" style="stop-color:#D1F383"/>
<stop offset="0.6509" style="stop-color:#C6EC71"/>
<stop offset="0.844" style="stop-color:#B7E257"/>
<stop offset="1" style="stop-color:#A8D73D"/>
<a:midPointStop offset="0" style="stop-color:#D9F991"/>
<a:midPointStop offset="0.7317" style="stop-color:#D9F991"/>
<a:midPointStop offset="1" style="stop-color:#A8D73D"/>
</linearGradient>
<rect x="16" y="10" style="fill:url(#SVGID_5_);" width="3" height="4"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="17.5" y1="11" x2="17.5" y2="13">
<stop offset="0" style="stop-color:#B3E810"/>
<stop offset="1" style="stop-color:#90C60D"/>
<a:midPointStop offset="0" style="stop-color:#B3E810"/>
<a:midPointStop offset="0.5" style="stop-color:#B3E810"/>
<a:midPointStop offset="1" style="stop-color:#90C60D"/>
</linearGradient>
<rect x="17" y="11" style="fill:url(#SVGID_6_);" width="1" height="2"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="8.4995" y1="0" x2="8.4995" y2="24.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<rect style="fill:url(#SVGID_7_);" width="17" height="24"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="8.4995" y1="1" x2="8.4995" y2="23.0005">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<rect x="1" y="1" style="fill:url(#SVGID_8_);" width="15" height="22"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="8.4995" y1="2" x2="8.4995" y2="22.0005">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<rect x="2" y="2" style="fill:url(#SVGID_9_);" width="13" height="20"/>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="17.5" y1="2" x2="17.5" y2="8">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<rect x="15" y="2" style="fill:url(#SVGID_10_);" width="5" height="6"/>
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="17.5" y1="3" x2="17.5" y2="7">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<rect x="16" y="3" style="fill:url(#SVGID_11_);" width="3" height="4"/>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="17.5" y1="4" x2="17.5" y2="6">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<rect x="17" y="4" style="fill:url(#SVGID_12_);" width="1" height="2"/>
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="12.4248" y1="10.7285" x2="12.4248" y2="15.9702">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="5.472010e-02" style="stop-color:#739DE9"/>
<stop offset="0.2045" style="stop-color:#6F95DE"/>
<stop offset="0.4149" style="stop-color:#6C91D7"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.13" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_13_);" d="M12.7,15.2C12,15.7,11.5,16,11,16c-0.3,0-0.5-0.1-0.7-0.3C10.1,15.5,10,15.3,10,15
c0-0.4,0.2-0.7,0.5-1c0.3-0.3,1-0.7,2.2-1.2v-0.5c0-0.4,0-0.6-0.1-0.7c0-0.1-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.1-0.4-0.1
c-0.2,0-0.4,0.1-0.6,0.2c-0.1,0.1-0.1,0.1-0.1,0.2c0,0.1,0,0.2,0.2,0.3c0.1,0.2,0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4
c-0.1,0.1-0.3,0.2-0.5,0.2c-0.2,0-0.4-0.1-0.6-0.2s-0.2-0.3-0.2-0.5c0-0.3,0.1-0.5,0.3-0.7c0.2-0.2,0.5-0.4,0.9-0.5s0.7-0.2,1.1-0.2
c0.5,0,0.9,0.1,1.1,0.3c0.3,0.2,0.5,0.4,0.5,0.7c0.1,0.2,0.1,0.5,0.1,1v1.9c0,0.2,0,0.4,0,0.4c0,0.1,0,0.1,0.1,0.1c0,0,0.1,0,0.1,0
c0.1,0,0.2-0.1,0.3-0.2l0.2,0.1c-0.2,0.3-0.4,0.5-0.6,0.6c-0.2,0.1-0.4,0.2-0.7,0.2c-0.3,0-0.5-0.1-0.7-0.2
C12.8,15.6,12.7,15.4,12.7,15.2z M12.7,14.8v-1.7c-0.4,0.3-0.8,0.5-1,0.8c-0.1,0.2-0.2,0.4-0.2,0.6c0,0.2,0.1,0.3,0.2,0.4
c0.1,0.1,0.2,0.1,0.4,0.1C12.2,15.1,12.4,15,12.7,14.8z"/>
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="6.3535" y1="8.2671" x2="6.3535" y2="15.7007">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="5.472010e-02" style="stop-color:#739DE9"/>
<stop offset="0.2045" style="stop-color:#6F95DE"/>
<stop offset="0.4149" style="stop-color:#6C91D7"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.13" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_14_);" d="M7,13.6H4.4l-0.3,0.7c-0.1,0.2-0.2,0.4-0.2,0.6c0,0.2,0.1,0.4,0.2,0.5
c0.1,0.1,0.3,0.1,0.7,0.1v0.2H2.5v-0.2c0.3,0,0.5-0.1,0.6-0.3c0.2-0.2,0.4-0.5,0.6-1.1l2.6-5.8h0.1l2.6,6c0.2,0.6,0.5,0.9,0.6,1.1
c0.1,0.1,0.3,0.2,0.5,0.2v0.2H6.7v-0.2h0.1c0.3,0,0.5,0,0.6-0.1c0.1-0.1,0.1-0.1,0.1-0.2c0-0.1,0-0.1,0-0.2c0,0-0.1-0.2-0.2-0.4
L7,13.6z M6.8,13.2l-1.1-2.5l-1.1,2.5H6.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" preserveAspectRatio="xMinYMid meet"><title>h5p finalArtboard 1</title><rect width="24" height="24" rx="3" ry="3" fill="#0882c8"/><path d="M22.1,8a3.37,3.37,0,0,0-2.42-.77H16.05v2H11.71l-.36,1.46a6.32,6.32,0,0,1,1-.35,3.49,3.49,0,0,1,.86-.06,3.24,3.24,0,0,1,2.35.88,2.93,2.93,0,0,1,.9,2.2A3.72,3.72,0,0,1,16,15.19a3.16,3.16,0,0,1-1.31,1.32,3.41,3.41,0,0,1-.67.27H17.7V13.28h1.65A3.8,3.8,0,0,0,22,12.46a3,3,0,0,0,.88-2.28A2.9,2.9,0,0,0,22.1,8Zm-2.44,3a1.88,1.88,0,0,1-1.21.29H17.7V9.2h.87a1.56,1.56,0,0,1,1.13.31,1,1,0,0,1,.3.76A.94.94,0,0,1,19.66,11Z" fill="#fff"/><path d="M12.27,12.05a1.33,1.33,0,0,0-1.19.74l-2.6-.37,1.17-5.2H7.29v4.08H4V7.23H1.1v9.55H4V13.28H7.29v3.49h3.57a3.61,3.61,0,0,1-1.13-.53A3.2,3.2,0,0,1,9,15.43a4,4,0,0,1-.48-1.09L11.09,14a1.32,1.32,0,1,0,1.18-1.92Z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 859 B

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 -2 24 24" style="overflow:visible;enable-background:new 0 -2 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="18" y1="10" x2="18" y2="20.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="13" y="10" style="fill:url(#SVGID_1_);" width="10" height="10"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="18" y1="11" x2="18" y2="19.0005">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<rect x="14" y="11" style="fill:url(#SVGID_2_);" width="8" height="8"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="18" y1="12" x2="18" y2="18">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="15" y="12" style="fill:url(#SVGID_3_);" width="6" height="6"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="18" y1="10" x2="18" y2="14.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="12" y="10" style="fill:url(#SVGID_4_);" width="12" height="4"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="18" y1="11" x2="18" y2="13.0005">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="13" y="11" style="fill:url(#SVGID_5_);" width="10" height="2"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="18" y1="10" x2="18" y2="16.4233">
<stop offset="0" style="stop-color:#8D470D"/>
<stop offset="1" style="stop-color:#7C3D09"/>
<a:midPointStop offset="0" style="stop-color:#8D470D"/>
<a:midPointStop offset="0.5" style="stop-color:#8D470D"/>
<a:midPointStop offset="1" style="stop-color:#7C3D09"/>
</linearGradient>
<polygon style="fill:url(#SVGID_6_);" points="17,10 17,11.7 17,15 17,16.4 18,15.5 19,16.4 19,15 19,11.7 19,10 "/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="6" y1="10" x2="6" y2="20.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="1" y="10" style="fill:url(#SVGID_7_);" width="10" height="10"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="6" y1="11" x2="6" y2="19.0005">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<rect x="2" y="11" style="fill:url(#SVGID_8_);" width="8" height="8"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="6" y1="12" x2="6" y2="18">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="3" y="12" style="fill:url(#SVGID_9_);" width="6" height="6"/>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="6" y1="10" x2="6" y2="14.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect y="10" style="fill:url(#SVGID_10_);" width="12" height="4"/>
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="6" y1="11" x2="6" y2="13.0005">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="1" y="11" style="fill:url(#SVGID_11_);" width="10" height="2"/>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="6" y1="10" x2="6" y2="16.4233">
<stop offset="0" style="stop-color:#8D470D"/>
<stop offset="1" style="stop-color:#7C3D09"/>
<a:midPointStop offset="0" style="stop-color:#8D470D"/>
<a:midPointStop offset="0.5" style="stop-color:#8D470D"/>
<a:midPointStop offset="1" style="stop-color:#7C3D09"/>
</linearGradient>
<polygon style="fill:url(#SVGID_12_);" points="5,10 5,11.7 5,15 5,16.4 6,15.5 7,16.4 7,15 7,11.7 7,10 "/>
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="10">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="7" style="fill:url(#SVGID_13_);" width="10" height="10"/>
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="1" x2="11.9995" y2="9">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<rect x="8" y="1" style="fill:url(#SVGID_14_);" width="8" height="8"/>
<linearGradient id="SVGID_15_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="2" x2="11.9995" y2="8">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="9" y="2" style="fill:url(#SVGID_15_);" width="6" height="6"/>
<linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="4">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="6" style="fill:url(#SVGID_16_);" width="12" height="4"/>
<linearGradient id="SVGID_17_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="1" x2="11.9995" y2="3">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="7" y="1" style="fill:url(#SVGID_17_);" width="10" height="2"/>
<linearGradient id="SVGID_18_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="6.4229">
<stop offset="0" style="stop-color:#8D470D"/>
<stop offset="1" style="stop-color:#7C3D09"/>
<a:midPointStop offset="0" style="stop-color:#8D470D"/>
<a:midPointStop offset="0.5" style="stop-color:#8D470D"/>
<a:midPointStop offset="1" style="stop-color:#7C3D09"/>
</linearGradient>
<polygon style="fill:url(#SVGID_18_);" points="11,0 11,1.7 11,5 11,6.4 12,5.5 13,6.4 13,5 13,1.7 13,0 "/>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 -2 24 24" style="overflow:visible;enable-background:new 0 -2 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="18" y1="10" x2="18" y2="20.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="13" y="10" style="fill:url(#SVGID_1_);" width="10" height="10"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="18" y1="11" x2="18" y2="19.0005">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<rect x="14" y="11" style="fill:url(#SVGID_2_);" width="8" height="8"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="18" y1="12" x2="18" y2="18">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="15" y="12" style="fill:url(#SVGID_3_);" width="6" height="6"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="18" y1="10" x2="18" y2="14.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="12" y="10" style="fill:url(#SVGID_4_);" width="12" height="4"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="18" y1="11" x2="18" y2="13.0005">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="13" y="11" style="fill:url(#SVGID_5_);" width="10" height="2"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="18" y1="10" x2="18" y2="16.4233">
<stop offset="0" style="stop-color:#8D470D"/>
<stop offset="1" style="stop-color:#7C3D09"/>
<a:midPointStop offset="0" style="stop-color:#8D470D"/>
<a:midPointStop offset="0.5" style="stop-color:#8D470D"/>
<a:midPointStop offset="1" style="stop-color:#7C3D09"/>
</linearGradient>
<polygon style="fill:url(#SVGID_6_);" points="17,10 17,11.7 17,15 17,16.4 18,15.5 19,16.4 19,15 19,11.7 19,10 "/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="6" y1="10" x2="6" y2="20.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="1" y="10" style="fill:url(#SVGID_7_);" width="10" height="10"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="6" y1="11" x2="6" y2="19.0005">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<rect x="2" y="11" style="fill:url(#SVGID_8_);" width="8" height="8"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="6" y1="12" x2="6" y2="18">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="3" y="12" style="fill:url(#SVGID_9_);" width="6" height="6"/>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="6" y1="10" x2="6" y2="14.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect y="10" style="fill:url(#SVGID_10_);" width="12" height="4"/>
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="6" y1="11" x2="6" y2="13.0005">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="1" y="11" style="fill:url(#SVGID_11_);" width="10" height="2"/>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="6" y1="10" x2="6" y2="16.4233">
<stop offset="0" style="stop-color:#8D470D"/>
<stop offset="1" style="stop-color:#7C3D09"/>
<a:midPointStop offset="0" style="stop-color:#8D470D"/>
<a:midPointStop offset="0.5" style="stop-color:#8D470D"/>
<a:midPointStop offset="1" style="stop-color:#7C3D09"/>
</linearGradient>
<polygon style="fill:url(#SVGID_12_);" points="5,10 5,11.7 5,15 5,16.4 6,15.5 7,16.4 7,15 7,11.7 7,10 "/>
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="10">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="7" style="fill:url(#SVGID_13_);" width="10" height="10"/>
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="1" x2="11.9995" y2="9">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<rect x="8" y="1" style="fill:url(#SVGID_14_);" width="8" height="8"/>
<linearGradient id="SVGID_15_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="2" x2="11.9995" y2="8">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="9" y="2" style="fill:url(#SVGID_15_);" width="6" height="6"/>
<linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="4">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="6" style="fill:url(#SVGID_16_);" width="12" height="4"/>
<linearGradient id="SVGID_17_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="1" x2="11.9995" y2="3">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="7" y="1" style="fill:url(#SVGID_17_);" width="10" height="2"/>
<linearGradient id="SVGID_18_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="6.4229">
<stop offset="0" style="stop-color:#8D470D"/>
<stop offset="1" style="stop-color:#7C3D09"/>
<a:midPointStop offset="0" style="stop-color:#8D470D"/>
<a:midPointStop offset="0.5" style="stop-color:#8D470D"/>
<a:midPointStop offset="1" style="stop-color:#7C3D09"/>
</linearGradient>
<polygon style="fill:url(#SVGID_18_);" points="11,0 11,1.7 11,5 11,6.4 12,5.5 13,6.4 13,5 13,1.7 13,0 "/>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="11.9854" y1="0" x2="11.9854" y2="24.0161">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M14.4,0L0.6,13.9c-0.8,0.8-0.8,2.1,0,2.8l6.7,6.7c0.8,0.8,2,0.8,2.8,0L24,9.6V0H14.4z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="11.9854" y1="1" x2="11.9854" y2="23.0161">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M8.7,23c-0.3,0-0.5-0.1-0.7-0.3L1.3,16C1.1,15.8,1,15.6,1,15.3c0-0.3,0.1-0.5,0.3-0.7L14.9,1
H23v8.2L9.4,22.7C9.2,22.9,9,23,8.7,23C8.7,23,8.7,23,8.7,23z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="11.9844" y1="2" x2="11.9844" y2="22.0142">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="2,15.3 15.3,2 22,2 22,8.8 8.7,22 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="8.7197" y1="9.9688" x2="8.7197" y2="20.6313">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<polygon style="fill:url(#SVGID_4_);" points="3.4,15.3 8.7,10 14.1,15.3 8.7,20.6 "/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="11.1465" y1="22.0312" x2="11.1465" y2="23.8711" gradientTransform="matrix(0.7071 -0.7071 0.7071 0.7071 -14.0909 8.2514)">
<stop offset="0" style="stop-color:#57C3F6"/>
<stop offset="0.1648" style="stop-color:#83D3F8"/>
<stop offset="0.3554" style="stop-color:#AFE3FB"/>
<stop offset="0.5396" style="stop-color:#D2EFFD"/>
<stop offset="0.7128" style="stop-color:#EBF8FE"/>
<stop offset="0.8709" style="stop-color:#FAFDFF"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0" style="stop-color:#57C3F6"/>
<a:midPointStop offset="0.3354" style="stop-color:#57C3F6"/>
<a:midPointStop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<polygon style="fill:url(#SVGID_5_);" points="7.4,17.9 8.7,19.2 12.6,15.3 11.3,14 "/>
<path style="fill:#F2EFD5;" d="M12.6,15.3L12.3,15c-0.3,0.2-0.8,0.5-1.4,1.1c-1.1,0.9-1,0.5-2,0.9c-0.4,0.2-0.9,0.6-1.4,1l1.2,1.2
L12.6,15.3z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="8.3882" y1="23.1035" x2="13.9038" y2="23.1035" gradientTransform="matrix(0.7071 -0.7071 0.7071 0.7071 -14.0909 8.2514)">
<stop offset="0" style="stop-color:#F8E5B5"/>
<stop offset="1" style="stop-color:#F9E5B6"/>
<a:midPointStop offset="0" style="stop-color:#F8E5B5"/>
<a:midPointStop offset="0.4451" style="stop-color:#F8E5B5"/>
<a:midPointStop offset="1" style="stop-color:#F9E5B6"/>
</linearGradient>
<path style="fill:url(#SVGID_6_);" d="M12.6,15.3l-0.2-0.2c-0.4,0.2-0.6,0.6-1.3,1.2C10,17.2,10,16.6,9,17c-0.4,0.2-0.9,0.6-1.3,1.1
l1.1,1.1L12.6,15.3z"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="12.1709" y1="17.6572" x2="10.1204" y2="22.7323" gradientTransform="matrix(0.7071 -0.7071 0.7071 0.7071 -14.0909 8.2514)">
<stop offset="0" style="stop-color:#57C3F6"/>
<stop offset="4.801393e-03" style="stop-color:#59C4F6"/>
<stop offset="0.1237" style="stop-color:#85D3F8"/>
<stop offset="0.2474" style="stop-color:#AAE1FA"/>
<stop offset="0.376" style="stop-color:#C9ECFC"/>
<stop offset="0.51" style="stop-color:#E1F4FD"/>
<stop offset="0.6519" style="stop-color:#F2FAFE"/>
<stop offset="0.807" style="stop-color:#FCFEFF"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0" style="stop-color:#57C3F6"/>
<a:midPointStop offset="0.25" style="stop-color:#57C3F6"/>
<a:midPointStop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<polygon style="fill:url(#SVGID_7_);" points="4.8,15.3 7.4,17.9 11.3,14 8.7,11.4 "/>
<path style="fill:#FFFFFF;" d="M9,11.6C8.7,11.8,8.7,12,8.7,12s-0.4-0.1-0.6,0.4c-0.1,0.4,0.3,0.5,0.3,0.5s0,0-0.1,0.1
c0,0.1,0.1,0.1,0.1,0.1s-0.2,0.1-0.1,0.4c0.1,0.3,0.3,0.3,0.3,0.3s-0.1,0.5,0.4,0.6c0.4,0.1,0.5-0.3,0.5-0.3s0.3,0,0.6-0.2
c0.3-0.2,0.4-0.5,0.4-0.5s0.1,0,0.2,0L9,11.6z"/>
<path style="fill:#FFFFFF;" d="M7.9,14.5c0,0-0.1,0-0.2,0.1c0,0.1,0.1,0.2,0.1,0.2s0,0,0,0c0,0,0,0,0,0s-0.1,0,0,0.1
C7.8,15,7.9,15,7.9,15s0,0.1,0.1,0.2c0.1,0,0.1-0.1,0.1-0.1s0.1,0,0.2,0c0.1-0.1,0.1-0.1,0.1-0.1s0.1,0,0.1-0.1c0-0.1,0-0.2,0-0.2
s0.2,0,0.1-0.2c-0.1-0.1-0.2,0-0.2,0s-0.1-0.1-0.1-0.1c-0.1,0-0.1,0.1-0.1,0.1s0,0,0,0c0,0-0.1,0-0.1,0S8,14.3,7.9,14.3
C7.8,14.4,7.9,14.5,7.9,14.5z"/>
<path style="fill:#FFFFFF;" d="M19.6,8.4l-5.5,5.5l-0.7-0.7l5.5-5.5L19.6,8.4z M17.5,6.3L12,11.8l0.7,0.7L18.2,7L17.5,6.3z
M16.1,4.9l-5.5,5.5l0.7,0.7l5.5-5.5L16.1,4.9z"/>
<ellipse style="fill:#FFFFFF;" cx="19.7" cy="4.3" rx="1.3" ry="1.3"/>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="5.5" y1="10" x2="5.5" y2="14.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<rect x="5" y="10" style="fill:url(#SVGID_1_);" width="1" height="4"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="15" y1="5" x2="15" y2="9">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="18,5 11,5 11,6 18,6 18,9 19,9 19,6 19,5 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="5.5" y1="14" x2="5.5" y2="24">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M10,14H1c-0.6,0-1,0.4-1,1v1v7v1h1h9h1v-1v-7v-1C11,14.4,10.6,14,10,14z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="5.5" y1="17" x2="5.5" y2="23">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<rect x="1" y="17" style="fill:url(#SVGID_4_);" width="9" height="6"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="5.5" y1="18" x2="5.5" y2="22">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<rect x="2" y="18" style="fill:url(#SVGID_5_);" width="7" height="4"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="5.5" y1="15" x2="5.5" y2="16">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<rect x="1" y="15" style="fill:url(#SVGID_6_);" width="9" height="1"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="18.5" y1="9" x2="18.5" y2="19.0005">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_7_);" d="M23,9h-9c-0.6,0-1,0.4-1,1v1v7v1h1h9h1v-1v-7v-1C24,9.4,23.6,9,23,9z"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="18.5" y1="12" x2="18.5" y2="18.0005">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<rect x="14" y="12" style="fill:url(#SVGID_8_);" width="9" height="6"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="18.5" y1="13" x2="18.5" y2="17.0005">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<rect x="15" y="13" style="fill:url(#SVGID_9_);" width="7" height="4"/>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="18.5" y1="10" x2="18.5" y2="11">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<rect x="14" y="10" style="fill:url(#SVGID_10_);" width="9" height="1"/>
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="5.5" y1="0" x2="5.5" y2="10">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_11_);" d="M10,0H1C0.4,0,0,0.4,0,1v1v7v1h1h9h1V9V2V1C11,0.4,10.6,0,10,0z"/>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="5.5" y1="3" x2="5.5" y2="9">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<rect x="1" y="3" style="fill:url(#SVGID_12_);" width="9" height="6"/>
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="5.5" y1="4" x2="5.5" y2="8">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<rect x="2" y="4" style="fill:url(#SVGID_13_);" width="7" height="4"/>
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="5.5" y1="1" x2="5.5" y2="2">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<rect x="1" y="1" style="fill:url(#SVGID_14_);" width="9" height="1"/>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="24.001">
<stop offset="0" style="stop-color:#90C50E"/>
<stop offset="1" style="stop-color:#70A034"/>
<a:midPointStop offset="0" style="stop-color:#90C50E"/>
<a:midPointStop offset="0.5" style="stop-color:#90C50E"/>
<a:midPointStop offset="1" style="stop-color:#70A034"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M21.1,12.3c1,0,1.7,0.9,1.7,0.9c0.3,0.4,0.7,0.8,0.9,0.8s0.3-0.5,0.3-1V8c0-0.5-0.5-1-1-1h-3
c-0.5,0-1-0.1-1-0.3c0-0.2,0.3-0.7,0.7-1.1c0,0,0.8-0.9,0.8-2.1C20.5,1.6,18.9,0,17,0s-3.5,1.6-3.5,3.5c0,1.2,0.8,2.1,0.8,2.1
C14.7,6,15,6.5,15,6.7C15,6.9,14.5,7,14,7H8C7.5,7,7,7.5,7,8v6c0,0.5-0.1,1-0.3,1S6,14.7,5.6,14.3c0,0-0.9-0.8-2.1-0.8
C1.6,13.5,0,15.1,0,17s1.6,3.5,3.5,3.5c1.2,0,2.1-0.8,2.1-0.8C6,19.3,6.5,19,6.7,19S7,19.5,7,20v3c0,0.5,0.5,1,1,1h4
c0.5,0,1-0.1,1-0.3s-0.3-0.6-0.7-1c0,0-0.6-0.6-0.6-1.6c0-1.4,1.3-2.5,2.7-2.5c1.4,0,2.4,1.1,2.4,2.5c0,1-0.9,1.7-0.9,1.7
c-0.4,0.3-0.8,0.7-0.8,0.9s0.5,0.3,1,0.3h7c0.5,0,1-0.5,1-1v-6c0-0.5-0.1-1-0.3-1s-0.6,0.3-1,0.7c0,0-0.6,0.6-1.6,0.6
c-1.4,0-2.5-1.3-2.5-2.7S19.7,12.3,21.1,12.3z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="1" x2="11.9995" y2="23.001">
<stop offset="0" style="stop-color:#D9F991"/>
<stop offset="0.2388" style="stop-color:#D7F88D"/>
<stop offset="0.4501" style="stop-color:#D1F383"/>
<stop offset="0.6509" style="stop-color:#C6EC71"/>
<stop offset="0.844" style="stop-color:#B7E257"/>
<stop offset="1" style="stop-color:#A8D73D"/>
<a:midPointStop offset="0" style="stop-color:#D9F991"/>
<a:midPointStop offset="0.7317" style="stop-color:#D9F991"/>
<a:midPointStop offset="1" style="stop-color:#A8D73D"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M17,23c0.4-0.4,0.7-1.1,0.7-1.9c0-2-1.5-3.5-3.4-3.5c-2,0-3.7,1.6-3.7,3.5
c0,0.9,0.3,1.5,0.6,1.9H8v-3c0-1.5-0.7-2-1.3-2c-0.6,0-1.3,0.6-1.7,0.9c0,0-0.7,0.6-1.5,0.6C2.1,19.5,1,18.4,1,17s1.1-2.5,2.5-2.5
c0.8,0,1.4,0.6,1.5,0.6C5.3,15.4,6.1,16,6.7,16C7.3,16,8,15.5,8,14V8h6c1.5,0,2-0.7,2-1.3c0-0.6-0.5-1.3-0.9-1.7
c0,0-0.6-0.7-0.6-1.5C14.5,2.1,15.6,1,17,1s2.5,1.1,2.5,2.5c0,0.8-0.6,1.5-0.6,1.5C18.5,5.4,18,6.1,18,6.7C18,7.3,18.5,8,20,8h3v4
c-0.4-0.4-1.1-0.7-1.9-0.7c-2,0-3.5,1.5-3.5,3.4c0,2,1.6,3.7,3.5,3.7c0.9,0,1.5-0.3,1.9-0.6V23H17z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="2" x2="11.9995" y2="22.001">
<stop offset="0" style="stop-color:#B3E810"/>
<stop offset="1" style="stop-color:#90C60D"/>
<a:midPointStop offset="0" style="stop-color:#B3E810"/>
<a:midPointStop offset="0.5" style="stop-color:#B3E810"/>
<a:midPointStop offset="1" style="stop-color:#90C60D"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M18.6,22c0.1-0.3,0.1-0.6,0.1-0.9c0-2.5-1.9-4.5-4.4-4.5c-2.5,0-4.7,2.1-4.7,4.5
c0,0.3,0,0.6,0.1,0.9H9v-2c0-2.1-1.2-3-2.3-3C7.8,17,9,16.1,9,14V9h5c2.1,0,3-1.2,3-2.3c0-0.7-0.4-1.5-1.2-2.4
c-0.1-0.1-0.3-0.5-0.3-0.8C15.5,2.7,16.2,2,17,2s1.5,0.7,1.5,1.5c0,0.3-0.3,0.7-0.3,0.8C17.4,5.2,17,6,17,6.7C17,7.8,17.9,9,20,9h2
v1.4c-0.3-0.1-0.6-0.1-0.9-0.1c-2.5,0-4.5,1.9-4.5,4.4c0,2.5,2.1,4.7,4.5,4.7c0.3,0,0.6,0,0.9-0.1V22H18.6z M3.5,18.5
C2.7,18.5,2,17.8,2,17s0.7-1.5,1.5-1.5c0.3,0,0.7,0.3,0.8,0.3C5.2,16.6,6,17,6.7,17c-0.7,0-1.5,0.4-2.4,1.2
C4.2,18.3,3.8,18.5,3.5,18.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-2 0 24 24" style="overflow:visible;enable-background:new -2 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="9.9995" y1="0" x2="9.9995" y2="24.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="12.5,0 11.5,0 1,0 0,0 0,24 1,24 19,24 20,24 20,7.9 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="9.9995" y1="1" x2="9.9995" y2="23.0005">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="12.1,1 11.1,1 2,1 1,1 1,23 2,23 18,23 19,23 19,8.3 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="9.9995" y1="2" x2="9.9995" y2="22.0005">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="11.6,2 10.6,2 3,2 2,2 2,22 3,22 17,22 18,22 18,8.7 "/>
<path style="fill:#FFFFFF;" d="M16,21H4v-1h12V21z M16,18H4v1h12V18z M16,16H4v1h12V16z M16,14H4v1h12V14z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="9.9995" y1="4" x2="9.9995" y2="12">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<polygon style="fill:url(#SVGID_4_);" points="15.3,4 14.3,4 4,4 3,4 3,12 4,12 16,12 17,12 17,5.8 "/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="9.9995" y1="9" x2="9.9995" y2="11">
<stop offset="0" style="stop-color:#57C3F6"/>
<stop offset="0.1648" style="stop-color:#83D3F8"/>
<stop offset="0.3554" style="stop-color:#AFE3FB"/>
<stop offset="0.5396" style="stop-color:#D2EFFD"/>
<stop offset="0.7128" style="stop-color:#EBF8FE"/>
<stop offset="0.8709" style="stop-color:#FAFDFF"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0" style="stop-color:#57C3F6"/>
<a:midPointStop offset="0.3354" style="stop-color:#57C3F6"/>
<a:midPointStop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<rect x="4" y="9" style="fill:url(#SVGID_5_);" width="12" height="2"/>
<path style="fill:#F2EFD5;" d="M12.4,10.9c-0.5-0.2-1.1-0.3-2.7-0.4c-1.5-0.1-0.9-0.4-2-0.8C6.7,9.2,4,9.4,4,9.4V11h8.6
C12.6,11,12.5,10.9,12.4,10.9z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="4" y1="10.2476" x2="12.2959" y2="10.2476">
<stop offset="0" style="stop-color:#F8E5B5"/>
<stop offset="1" style="stop-color:#F9E5B6"/>
<a:midPointStop offset="0" style="stop-color:#F8E5B5"/>
<a:midPointStop offset="0.4451" style="stop-color:#F8E5B5"/>
<a:midPointStop offset="1" style="stop-color:#F9E5B6"/>
</linearGradient>
<path style="fill:url(#SVGID_6_);" d="M9.7,10.7c-1.5-0.1-0.9-0.6-2-1c-1-0.4-3.7,0-3.7,0V11h8.3C11.5,11,11.2,10.8,9.7,10.7z"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="12.5342" y1="1.5674" x2="8.163" y2="10.9413">
<stop offset="0" style="stop-color:#57C3F6"/>
<stop offset="0.2292" style="stop-color:#8AD5F9"/>
<stop offset="0.4827" style="stop-color:#BCE7FB"/>
<stop offset="0.705" style="stop-color:#E1F4FD"/>
<stop offset="0.885" style="stop-color:#F7FCFF"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0" style="stop-color:#57C3F6"/>
<a:midPointStop offset="0.3902" style="stop-color:#57C3F6"/>
<a:midPointStop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<rect x="4" y="5" style="fill:url(#SVGID_7_);" width="12" height="4"/>
<path style="fill:#FFFFFF;" d="M11.2,5.6c0,0-0.2-0.4-0.7-0.2c-0.4,0.3-0.1,0.7-0.1,0.7s-0.1,0-0.1,0c-0.1,0,0,0.2,0,0.2
s-0.3,0-0.4,0.2C9.6,6.9,9.8,7,9.8,7S9.3,7.4,9.6,7.8c0.3,0.4,0.6,0.1,0.6,0.1s0.2,0.3,0.6,0.3c0.4,0.1,0.6-0.1,0.6-0.1
s0.3,0.2,0.5,0.1C12.4,8.2,12.5,8,12.5,8s0.6,0.3,0.8-0.3C13.4,7.3,13,7.1,13,7.1s0.1-0.4-0.2-0.6s-0.6,0-0.6,0s0.1,0,0-0.1
c0-0.1-0.3-0.1-0.3-0.1s0.1-0.6-0.2-0.8C11.4,5.3,11.2,5.6,11.2,5.6z"/>
<path style="fill:#FFFFFF;" d="M8.6,6.9c0,0-0.1-0.1-0.2,0C8.3,6.9,8.4,7,8.4,7s0,0,0,0c0,0,0,0,0,0s-0.1,0-0.1,0.1
c0,0.1,0,0.1,0,0.1s-0.1,0.1,0,0.2c0.1,0.1,0.2,0,0.2,0s0.1,0.1,0.2,0.1c0.1,0,0.2,0,0.2,0s0.1,0.1,0.2,0C9,7.6,9,7.6,9,7.6
s0.2,0.1,0.2-0.1c0-0.1-0.1-0.2-0.1-0.2s0-0.1-0.1-0.2c-0.1,0-0.2,0-0.2,0s0,0,0,0s-0.1,0-0.1,0s0-0.2-0.1-0.2
C8.7,6.8,8.6,6.9,8.6,6.9z"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="15.2451" y1="0" x2="15.2451" y2="9.3594">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_8_);" d="M11,9c0,0,5.2-1.5,9,0.4c0-0.1,0-1.5,0-1.5L12.5,0c0,0-1.8,0-2,0C13.1,3.7,11,9,11,9z"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="12.3223" y1="7.5449" x2="15.4504" y2="4.4168">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="0.5181" style="stop-color:#E5F3FC"/>
<stop offset="0.7045" style="stop-color:#DEF0FB"/>
<stop offset="0.8371" style="stop-color:#D3EBFA"/>
<stop offset="0.872" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BDD8F0"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.87" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.872" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BDD8F0"/>
</linearGradient>
<path style="fill:url(#SVGID_9_);" d="M18.5,7.8c-0.9-0.2-2-0.3-3.1-0.3c-1.1,0-2.1,0.1-3,0.3c0.4-1.6,0.7-4.1-0.2-6.4L18.5,7.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="11.9565" y1="0" x2="11.9565" y2="24.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="14.4,0 13.4,0 3,0 2,0 2,24 3,24 21,24 22,24 22,7.9 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="11.9565" y1="1" x2="11.9565" y2="23.0005">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="14,1 13,1 4,1 3,1 3,23 4,23 20,23 21,23 21,8.3 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="11.9565" y1="2" x2="11.9565" y2="22.0005">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="13.6,2 12.6,2 5,2 4,2 4,22 5,22 19,22 20,22 20,8.7 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="17.2021" y1="0" x2="17.2021" y2="9.3594">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M13,9c0,0,5.2-1.5,9,0.4c0-0.1,0-1.5,0-1.5L14.4,0c0,0-1.8,0-2,0C15.1,3.7,13,9,13,9z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="14.2793" y1="7.5449" x2="17.4074" y2="4.4168">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="0.5181" style="stop-color:#E5F3FC"/>
<stop offset="0.7045" style="stop-color:#DEF0FB"/>
<stop offset="0.8371" style="stop-color:#D3EBFA"/>
<stop offset="0.872" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BDD8F0"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.87" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.872" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BDD8F0"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M20.4,7.8c-0.9-0.2-2-0.3-3.1-0.3c-1.1,0-2.1,0.1-3,0.3c0.4-1.6,0.7-4.1-0.2-6.4L20.4,7.8z"/>
<g>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="11.8569" y1="1.8521" x2="11.8569" y2="23.9487">
<stop offset="0" style="stop-color:#DB6D17"/>
<stop offset="1" style="stop-color:#BF3B08"/>
<a:midPointStop offset="0" style="stop-color:#DB6D17"/>
<a:midPointStop offset="0.5" style="stop-color:#DB6D17"/>
<a:midPointStop offset="1" style="stop-color:#BF3B08"/>
</linearGradient>
<polygon style="fill:url(#SVGID_6_);" points="8,16.9 3.2,11.9 0,15.1 8.8,23.9 23.7,1.9 "/>
</g>
<g>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="9.5522" y1="9.0078" x2="9.5522" y2="22.3833">
<stop offset="0" style="stop-color:#F6A55E"/>
<stop offset="1" style="stop-color:#EA5B03"/>
<a:midPointStop offset="0" style="stop-color:#F6A55E"/>
<a:midPointStop offset="0.5" style="stop-color:#F6A55E"/>
<a:midPointStop offset="1" style="stop-color:#EA5B03"/>
</linearGradient>
<polygon style="fill:url(#SVGID_7_);" points="1.4,15.1 3.2,13.4 7.9,18.4 17.7,9 8.7,22.4 "/>
</g>
<g>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="7.2485" y1="14.7998" x2="7.2485" y2="20.8174">
<stop offset="0" style="stop-color:#F17219"/>
<stop offset="1" style="stop-color:#EA5B03"/>
<a:midPointStop offset="0" style="stop-color:#F17219"/>
<a:midPointStop offset="0.5" style="stop-color:#F17219"/>
<a:midPointStop offset="1" style="stop-color:#EA5B03"/>
</linearGradient>
<polygon style="fill:url(#SVGID_8_);" points="2.8,15.1 3.1,14.8 7.9,19.8 11.7,16.2 8.5,20.8 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="-3 0 24 24" style="overflow:visible;enable-background:new -3 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="9.4995" y1="0" x2="9.4995" y2="24.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="11.5,0 0,0 0,24 19,24 19,7.9 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="9.4995" y1="1" x2="9.4995" y2="23.0005">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="1,23 1,1 11.1,1 18,8.3 18,23 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="9.4995" y1="2" x2="9.4995" y2="22.0005">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="2,22 2,2 10.6,2 17,8.7 17,22 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="14.2451" y1="0" x2="14.2451" y2="9.3594">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M10,9c0,0,5.2-1.5,9,0.4c0-0.1,0-1.5,0-1.5L11.5,0c0,0-1.8,0-2,0C12.1,3.7,10,9,10,9z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="11.3223" y1="7.5449" x2="14.4504" y2="4.4168">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="0.5181" style="stop-color:#E5F3FC"/>
<stop offset="0.7045" style="stop-color:#DEF0FB"/>
<stop offset="0.8371" style="stop-color:#D3EBFA"/>
<stop offset="0.872" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BDD8F0"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.87" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.872" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BDD8F0"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M17.5,7.8c-0.9-0.2-2-0.3-3.1-0.3c-1.1,0-2.1,0.1-3,0.3c0.4-1.6,0.7-4.1-0.2-6.4L17.5,7.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 -2 24 24" style="overflow:visible;enable-background:new 0 -2 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="20.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect x="1" style="fill:url(#SVGID_1_);" width="22" height="20"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="1" x2="11.9995" y2="19.0005">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<rect x="2" y="1" style="fill:url(#SVGID_2_);" width="20" height="18"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="2" x2="11.9995" y2="18.0005">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="3" y="2" style="fill:url(#SVGID_3_);" width="18" height="16"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="7">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect style="fill:url(#SVGID_4_);" width="24" height="7"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="1" x2="11.9995" y2="6">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<rect x="1" y="1" style="fill:url(#SVGID_5_);" width="22" height="5"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="2" x2="11.9995" y2="5">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="2" y="2" style="fill:url(#SVGID_6_);" width="20" height="3"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="11.4995" y1="0" x2="11.4995" y2="13.0005">
<stop offset="0" style="stop-color:#8D470D"/>
<stop offset="1" style="stop-color:#7C3D09"/>
<a:midPointStop offset="0" style="stop-color:#8D470D"/>
<a:midPointStop offset="0.5" style="stop-color:#8D470D"/>
<a:midPointStop offset="1" style="stop-color:#7C3D09"/>
</linearGradient>
<polygon style="fill:url(#SVGID_7_);" points="9,0 9,3 9,10 9,13 11.5,11 14,13 14,10 14,3 14,0 "/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="11.4995" y1="1" x2="11.4995" y2="10.9355">
<stop offset="0" style="stop-color:#D58738"/>
<stop offset="1" style="stop-color:#AB551F"/>
<a:midPointStop offset="0" style="stop-color:#D58738"/>
<a:midPointStop offset="0.5" style="stop-color:#D58738"/>
<a:midPointStop offset="1" style="stop-color:#AB551F"/>
</linearGradient>
<polygon style="fill:url(#SVGID_8_);" points="10,1 10,4 10,7.9 10,10.9 11.5,9.7 13,10.9 13,7.9 13,4 13,1 "/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="11.5" y1="2" x2="11.5" y2="8.8711">
<stop offset="0" style="stop-color:#D0813A"/>
<stop offset="1" style="stop-color:#AF551D"/>
<a:midPointStop offset="0" style="stop-color:#D0813A"/>
<a:midPointStop offset="0.5" style="stop-color:#D0813A"/>
<a:midPointStop offset="1" style="stop-color:#AF551D"/>
</linearGradient>
<polygon style="fill:url(#SVGID_9_);" points="11,2 11,5 11,5.8 11,8.8 11.5,8.4 12,8.9 12,5.9 12,5 12,2 "/>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="20" y1="8" x2="20" y2="24.0005">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<rect x="16" y="8" style="fill:url(#SVGID_1_);" width="8" height="16"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="20" y1="9" x2="20" y2="23.0005">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<rect x="17" y="9" style="fill:url(#SVGID_2_);" width="6" height="14"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="20" y1="10" x2="20" y2="22.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<rect x="18" y="10" style="fill:url(#SVGID_3_);" width="4" height="12"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="0" x2="11.9995" y2="24.0005">
<stop offset="0" style="stop-color:#90C50E"/>
<stop offset="1" style="stop-color:#70A034"/>
<a:midPointStop offset="0" style="stop-color:#90C50E"/>
<a:midPointStop offset="0.5" style="stop-color:#90C50E"/>
<a:midPointStop offset="1" style="stop-color:#70A034"/>
</linearGradient>
<rect x="8" style="fill:url(#SVGID_4_);" width="8" height="24"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="1" x2="11.9995" y2="23.0005">
<stop offset="0" style="stop-color:#D9F991"/>
<stop offset="0.2388" style="stop-color:#D7F88D"/>
<stop offset="0.4501" style="stop-color:#D1F383"/>
<stop offset="0.6509" style="stop-color:#C6EC71"/>
<stop offset="0.844" style="stop-color:#B7E257"/>
<stop offset="1" style="stop-color:#A8D73D"/>
<a:midPointStop offset="0" style="stop-color:#D9F991"/>
<a:midPointStop offset="0.7317" style="stop-color:#D9F991"/>
<a:midPointStop offset="1" style="stop-color:#A8D73D"/>
</linearGradient>
<rect x="9" y="1" style="fill:url(#SVGID_5_);" width="6" height="22"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="11.9995" y1="2" x2="11.9995" y2="22.0005">
<stop offset="0" style="stop-color:#B3E810"/>
<stop offset="1" style="stop-color:#90C60D"/>
<a:midPointStop offset="0" style="stop-color:#B3E810"/>
<a:midPointStop offset="0.5" style="stop-color:#B3E810"/>
<a:midPointStop offset="1" style="stop-color:#90C60D"/>
</linearGradient>
<rect x="10" y="2" style="fill:url(#SVGID_6_);" width="4" height="20"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="4" y1="12" x2="4" y2="24.0005">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<rect y="12" style="fill:url(#SVGID_7_);" width="8" height="12"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="4" y1="13" x2="4" y2="23.0005">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<rect x="1" y="13" style="fill:url(#SVGID_8_);" width="6" height="10"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="4" y1="14" x2="4" y2="22">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<rect x="2" y="14" style="fill:url(#SVGID_9_);" width="4" height="8"/>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,485 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="11.9653" y1="0" x2="11.9653" y2="24.0005">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="14.4,0 13.4,0 3,0 2,0 2,24 3,24 21,24 22,24 22,7.9 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="11.9653" y1="1" x2="11.9653" y2="23.0005">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="1" style="stop-color:#DEEFFC"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.5" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="1" style="stop-color:#DEEFFC"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="14,1 13,1 4,1 3,1 3,23 4,23 20,23 21,23 21,8.3 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="11.9653" y1="2" x2="11.9653" y2="22.0005">
<stop offset="0" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BBDFF8"/>
<a:midPointStop offset="0" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BBDFF8"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="13.6,2 12.6,2 5,2 4,2 4,22 5,22 19,22 20,22 20,8.7 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="17.2109" y1="0" x2="17.2109" y2="9.3594">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M13,9c0,0,5.2-1.5,9,0.4c0-0.1,0-1.5,0-1.5L14.4,0c0,0-1.8,0-2,0C15.1,3.7,13,9,13,9z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="14.2881" y1="7.5449" x2="17.4162" y2="4.4168">
<stop offset="0" style="stop-color:#E7F4FC"/>
<stop offset="0.5181" style="stop-color:#E5F3FC"/>
<stop offset="0.7045" style="stop-color:#DEF0FB"/>
<stop offset="0.8371" style="stop-color:#D3EBFA"/>
<stop offset="0.872" style="stop-color:#CEE9F9"/>
<stop offset="1" style="stop-color:#BDD8F0"/>
<a:midPointStop offset="0" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.87" style="stop-color:#E7F4FC"/>
<a:midPointStop offset="0.872" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="0.5" style="stop-color:#CEE9F9"/>
<a:midPointStop offset="1" style="stop-color:#BDD8F0"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M20.5,7.8c-0.9-0.2-2-0.3-3.1-0.3c-1.1,0-2.1,0.1-3,0.3c0.4-1.6,0.7-4.1-0.2-6.4L20.5,7.8z"/>
<radialGradient id="SVGID_6_" cx="7.5312" cy="16.5" r="7.0005" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#8BB4F0"/>
<stop offset="7.721406e-02" style="stop-color:#88B1EF"/>
<stop offset="0.488" style="stop-color:#7FA7EC"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#8BB4F0"/>
<a:midPointStop offset="0.25" style="stop-color:#8BB4F0"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</radialGradient>
<path style="fill:url(#SVGID_6_);" d="M7.5,23.5C0.4,23.2-1.6,15.1,3,11.2c1.8-1.6,3.8-1.7,4.6-1.7c0.4,0,0.8,0,1.1,0.1
C9,9.6,9.4,9.7,9.7,9.8c0.3,0.1,0.7,0.3,1,0.4c0.3,0.2,0.6,0.4,0.9,0.6c0.3,0.2,0.6,0.4,0.8,0.7c0.3,0.3,0.5,0.5,0.7,0.8
c0.2,0.3,0.4,0.6,0.6,0.9c0.2,0.3,0.3,0.7,0.4,1c0.1,0.3,0.2,0.7,0.3,1.1c0.1,0.4,0.1,0.8,0.1,1.1s0,0.8-0.1,1.1
c-0.1,0.4-0.1,0.7-0.3,1.1c-0.1,0.3-0.3,0.7-0.4,1c-0.2,0.3-0.4,0.6-0.6,0.9c-0.2,0.3-0.4,0.6-0.7,0.8c-0.3,0.3-0.5,0.5-0.8,0.7
c-0.3,0.2-0.6,0.4-0.9,0.6c-0.3,0.2-0.7,0.3-1,0.4c-0.3,0.1-0.7,0.2-1.1,0.3C8.3,23.5,7.9,23.5,7.5,23.5"/>
<radialGradient id="SVGID_7_" cx="7.5308" cy="16.5" r="6.4856" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#8BB4F0"/>
<stop offset="7.721406e-02" style="stop-color:#88B1EF"/>
<stop offset="0.488" style="stop-color:#7FA7EC"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#8BB4F0"/>
<a:midPointStop offset="0.25" style="stop-color:#8BB4F0"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</radialGradient>
<path style="fill:url(#SVGID_7_);" d="M7.5,23C1,22.7-0.9,15.2,3.3,11.6C5,10.1,6.8,10,7.5,10c0.4,0,0.7,0,1.1,0.1
c0.3,0.1,0.7,0.1,1,0.2s0.6,0.2,0.9,0.4c0.3,0.2,0.6,0.3,0.9,0.5c0.3,0.2,0.5,0.4,0.8,0.6c0.2,0.2,0.5,0.5,0.6,0.8
c0.2,0.3,0.4,0.6,0.5,0.9s0.3,0.6,0.4,0.9s0.2,0.7,0.2,1c0.1,0.3,0.1,0.7,0.1,1.1c0,0.4,0,0.7-0.1,1.1c-0.1,0.3-0.1,0.7-0.2,1
s-0.2,0.6-0.4,0.9s-0.3,0.6-0.5,0.9s-0.4,0.5-0.6,0.8s-0.5,0.5-0.8,0.6c-0.3,0.2-0.6,0.4-0.9,0.5c-0.3,0.2-0.6,0.3-0.9,0.4
s-0.7,0.2-1,0.2C8.2,23,7.9,23,7.5,23"/>
<path style="fill:#B3E710;" d="M6.1,10.8L6.1,10.8c0-0.1,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0,0,0,0C6.1,10.7,6,10.7,6.1,10.8
c-0.1-0.1-0.1,0-0.2,0C5.9,10.7,6,10.8,6.1,10.8C6,10.8,6.1,10.8,6.1,10.8 M3,13c0,0,2,0,3.6,0c0,0,0.4-0.1,0.4-0.1l0,0v0l0,0v0v0
l0,0v0l0,0c0-0.1-0.3-0.1-0.4-0.1c0-0.1-0.2-0.2-0.1-0.4c0,0-0.1,0-0.1,0c-0.1-0.1-0.3-0.1-0.4-0.1c-0.1,0-0.2-0.1-0.3-0.2
c-0.1,0-0.1,0-0.2,0c0,0,0,0,0,0C5.6,12,5.6,12,5.4,11.9c0-0.2,0.2-0.3,0.4-0.5c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0.1,0,0.1,0
c0,0,0,0,0,0c0,0,0,0-0.1-0.1c0,0,0,0,0,0v0c0,0,0.1,0.1,0.2,0c0,0,0,0,0-0.1c0.1,0,0.1,0,0.2-0.1c0,0,0,0,0,0
c-0.1,0-0.1-0.1-0.2-0.1c0.1,0,0.1,0,0.2,0.1c0,0,0.1,0,0.2-0.1c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0
c0,0,0.1,0,0.1,0c0,0,0,0,0-0.1l0,0c0,0,0,0.1,0.1,0.1c0.1-0.1,0.1-0.1,0.2-0.1c0-0.1,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0-0.1c-0.1,0-0.1-0.1-0.2,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.2,0.2-0.2,0.2c-0.1,0,0-0.1,0-0.1c0.1,0,0.1,0,0-0.1
c-0.1,0-0.1,0.1-0.1,0.1c0,0,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0.1-0.1c0,0,0-0.1,0-0.1c0,0,0,0,0,0
c-0.1,0-0.2,0.1-0.2,0.1c0,0,0,0,0,0c-0.1,0-0.1,0-0.1,0.1c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0l-0.3,0.2c0,0,0,0,0,0c0,0,0,0,0.1-0.1c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0h0l0,0l0,0h0l0,0l0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0-0.1,0-0.3,0h0l0,0c0,0,0,0,0,0h0l0,0c0,0,0,0,0,0h0l0,0c-0.2,0.1-0.2,0.1-0.3,0.2
c0-0.1,0-0.1,0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0-0.2,0-0.2,0c0.1,0,0.1,0,0.2-0.1c-0.1,0-0.1,0-0.1-0.1c-0.1,0-0.1,0-0.1,0
c0,0,0,0,0.1,0c0,0-0.1,0-0.1,0c0,0,0,0,0.1-0.1c0,0-0.3,0.1-0.5,0.2c0,0,0,0,0.1,0c0.1,0,0.2,0.1,0.3,0c0,0-0.1,0.2-0.1,0.2
c0,0,0,0,0,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0.4,0,0.4,0v-0.1c0-0.1-0.1-0.2,0-0.2c0,0-0.1,0-0.1,0c0,0,0,0,0-0.1
c-0.1,0-0.2,0.1-0.3,0.1c-0.3,0.1-0.6,0.4-0.9,0.5c-0.1,0.1-0.3,0.2-0.4,0.3c0,0,0,0-0.1,0l0,0L3,11.8l0,0c0.1-0.1,0.1-0.1,0.2-0.2
c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0.1-0.1,0.1-0.1,0.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0.1,0,0.1,0
c0,0,0,0.1,0,0.1c0,0,0,0,0,0c-0.1,0-0.1,0-0.1,0.1l0,0C3,12.2,3,12.2,2.9,12.3c0,0,0,0,0,0v0c0,0.1-0.1,0.1-0.1,0.1c0,0,0,0,0.1,0
l0,0c0,0.1-0.1,0.1-0.1,0.2c0,0,0,0.2,0,0.2c0,0,0,0.3-0.1,0.3c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0h0c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0.1c0,0,0,0,0,0
c0,0,0,0,0,0 M8.3,10.4C8.3,10.4,8.3,10.4,8.3,10.4L8.3,10.4C8.3,10.4,8.3,10.4,8.3,10.4C8.3,10.4,8.3,10.4,8.3,10.4 M8.3,10.4
c0,0-0.1-0.1-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0C8.2,10.4,8.2,10.4,8.3,10.4
C8.2,10.4,8.2,10.4,8.3,10.4L8.3,10.4L8.3,10.4L8.3,10.4C8.2,10.5,8.2,10.5,8.3,10.4C8.2,10.5,8.2,10.5,8.3,10.4
C8.2,10.5,8.3,10.5,8.3,10.4C8.3,10.4,8.3,10.4,8.3,10.4 M5.9,10.7L5.9,10.7C6,10.6,6,10.6,5.9,10.7c0-0.1,0-0.1,0-0.1
c0,0,0,0,0.1-0.1c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0-0.1,0.1-0.2,0.1c0,0,0,0,0.1-0.1c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0.1-0.1,0.1-0.1,0.1c0.1,0,0.1,0,0.1,0c0,0-0.1,0-0.1,0c0,0,0.1,0,0.1-0.1c0,0-0.3,0.1-0.4,0.1
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0.1,0c0.1,0,0.1,0,0.1,0
c-0.1,0-0.1,0-0.2,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0.1c0,0,0.1,0,0.2,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0.1,0
c0,0,0,0,0,0c0,0.1,0.1,0.1,0.1,0.1C5.8,10.7,5.8,10.7,5.9,10.7c-0.1,0-0.1,0-0.1,0c0,0,0,0,0,0C5.8,10.7,5.9,10.7,5.9,10.7
C5.9,10.7,5.9,10.7,5.9,10.7 M8.2,11L8.2,11C8.2,11,8.2,11,8.2,11c-0.1,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1-0.1-0.1-0.1
c0,0-0.1,0-0.1,0c0-0.1-0.1,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0v0l0,0c0,0,0.1,0,0.1,0c0,0-0.1,0-0.1-0.1c0,0,0,0,0.1,0
c0,0,0,0-0.1,0c0,0,0,0-0.1,0c0,0,0,0,0,0l0,0c0,0,0,0,0.1,0c0,0-0.1,0-0.1,0c0,0,0,0-0.1,0c0,0,0,0,0,0v0c0,0,0,0-0.1,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0l0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c-0.1-0.1-0.1-0.1-0.1-0.1c0,0,0,0.1-0.1,0.1c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0-0.1-0.2,0c0,0,0,0,0,0c-0.1,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0-0.1,0
c0,0,0,0,0,0c0,0,0,0,0,0c0-0.1,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0.1-0.1,0.1-0.1c0,0,0,0-0.1,0c-0.1,0-0.2,0.1-0.2,0.1c0,0,0,0,0,0
c0,0-0.1,0-0.1,0.1c0.1,0,0.1,0,0.1,0c0,0-0.1,0-0.1,0c0,0.1,0.1,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0
c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0.1,0,0.1,0,0.1,0c0,0-0.1,0-0.1,0l0,0c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0.1,0.1
c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0
s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0.1,0,0.1c0,0-0.1,0.1-0.1,0.1c0,0,0,0-0.1,0
c0,0,0.1,0,0.1,0.1c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0h0c-0.1-0.1-0.1,0-0.1,0c0,0,0,0,0,0c-0.1,0-0.1,0-0.1,0.1
c0.1,0,0.2,0,0.2,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0h0c0,0,0,0,0,0l0,0h0h0h0l0,0
c0,0,0,0,0.1,0.1c0,0,0,0,0,0c0,0.1,0.1,0,0.1,0.1c0,0,0.1,0,0.1,0.1c0.1,0,0.1,0,0.2,0c0,0,0,0,0,0c0-0.1-0.1-0.1-0.2-0.1
c-0.1,0-0.1,0-0.1-0.1c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0.1c0,0,0,0,0-0.1
c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1-0.1-0.1-0.1c0,0,0,0.1,0.1,0.1c0,0,0,0-0.1-0.1c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0-0.1,0-0.1-0.1
c0,0,0,0,0,0l0,0c0,0,0-0.1-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0-0.1c0.1,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0C7.9,11,7.9,11,8,11.1
c0,0,0,0.1,0.1,0c0,0.1,0,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0-0.1c0,0,0,0,0,0c0,0,0,0,0,0C8.2,11.1,8.2,11.1,8.2,11
C8.2,11,8.2,11,8.2,11C8.2,11,8.2,11,8.2,11C8.2,11,8.2,11,8.2,11 M6.4,10.4L6.4,10.4C6.5,10.4,6.4,10.4,6.4,10.4
C6.4,10.4,6.4,10.4,6.4,10.4c0-0.1,0.1-0.1,0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0
l0,0L6.4,10.4C6.4,10.4,6.4,10.4,6.4,10.4c-0.1-0.1-0.1,0-0.1,0c0,0,0,0,0,0c-0.1,0-0.1,0.1-0.1,0.1c0,0,0,0,0,0.1c0,0,0,0,0.1,0
c0,0,0,0,0,0c0,0,0,0,0,0C6.3,10.5,6.3,10.5,6.4,10.4C6.3,10.5,6.4,10.5,6.4,10.4 M8.1,10.3C8.1,10.3,8.1,10.3,8.1,10.3
C8.1,10.3,8.1,10.3,8.1,10.3C8.1,10.3,8.1,10.3,8.1,10.3C8.1,10.3,8.1,10.3,8.1,10.3 M6.8,10.3C6.8,10.3,6.8,10.3,6.8,10.3
C6.8,10.3,6.8,10.3,6.8,10.3C6.8,10.3,6.8,10.3,6.8,10.3C6.8,10.3,6.7,10.3,6.8,10.3C6.7,10.3,6.7,10.3,6.8,10.3c-0.1,0-0.1,0-0.2,0
c0,0,0,0-0.1,0c0,0,0,0.1-0.1,0.1c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0C6.6,10.4,6.8,10.3,6.8,10.3 M8.1,10.3
C8.1,10.3,8.1,10.3,8.1,10.3L8.1,10.3C8.1,10.3,8.1,10.3,8.1,10.3L8.1,10.3 M5.9,10.3L5.9,10.3C6,10.3,6,10.3,5.9,10.3
C6,10.3,6,10.3,5.9,10.3C6,10.3,6,10.3,5.9,10.3C6,10.3,6,10.3,5.9,10.3C6,10.3,6,10.3,5.9,10.3C6,10.3,5.9,10.3,5.9,10.3
c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1,0-0.2,0.1l0,0l0,0l0,0h0l0,0l0,0l0,0l0,0c0,0-0.1,0-0.1,0.1c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0-0.1,0-0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0.1,0,0.1-0.1,0.2-0.1c0,0,0.1,0,0.1,0
C5.8,10.4,5.9,10.4,5.9,10.3 M7.9,10.2C7.9,10.2,7.9,10.2,7.9,10.2C7.9,10.2,7.9,10.2,7.9,10.2C7.9,10.2,7.9,10.2,7.9,10.2
C7.9,10.2,7.9,10.2,7.9,10.2 M7.2,10.2L7.2,10.2C7.2,10.2,7.2,10.2,7.2,10.2C7.2,10.2,7.2,10.2,7.2,10.2C7.2,10.2,7.2,10.2,7.2,10.2
c-0.1,0-0.1,0-0.2,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0.1,0,0.1,0,0.1,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0C7.1,10.3,7.1,10.3,7.2,10.2C7.2,10.2,7.2,10.2,7.2,10.2 M6.8,10.2C6.8,10.1,6.8,10.1,6.8,10.2
C6.8,10.1,6.8,10.1,6.8,10.2C6.8,10.1,6.8,10.1,6.8,10.2C6.8,10.2,6.8,10.2,6.8,10.2C6.8,10.1,6.8,10.1,6.8,10.2
C6.8,10.1,6.8,10.1,6.8,10.2C6.8,10.1,6.8,10.1,6.8,10.2C6.8,10.2,6.7,10.2,6.8,10.2L6.8,10.2C6.7,10.2,6.7,10.2,6.8,10.2
C6.7,10.1,6.7,10.2,6.8,10.2C6.7,10.2,6.7,10.2,6.8,10.2c-0.1,0-0.1,0-0.1,0c0,0,0,0,0,0s0,0,0,0s0,0,0,0c0,0,0,0,0,0s0,0,0,0
s0,0,0,0s0,0,0,0s0,0,0,0c0,0,0,0,0,0C6.6,10.2,6.7,10.2,6.8,10.2C6.7,10.2,6.7,10.2,6.8,10.2c-0.1,0-0.1,0-0.1,0c0,0-0.1,0-0.1,0
c0,0,0,0,0.1,0C6.7,10.2,6.7,10.2,6.8,10.2 M6.5,10.2C6.6,10.2,6.6,10.2,6.5,10.2C6.6,10.2,6.6,10.2,6.5,10.2
C6.6,10.2,6.5,10.2,6.5,10.2C6.5,10.2,6.5,10.2,6.5,10.2c0,0,0.1,0,0.1-0.1c0,0,0,0,0,0C6.6,10.1,6.6,10.1,6.5,10.2
c-0.1,0-0.1,0-0.1,0C6.4,10.2,6.4,10.2,6.5,10.2c-0.1,0-0.1,0-0.1,0C6.5,10.2,6.5,10.2,6.5,10.2c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1,0
c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0.1,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0.1,0,0.1,0,0.1,0c0,0,0,0,0,0
c0,0,0,0,0,0C6.5,10.2,6.5,10.2,6.5,10.2 M6.9,10.1C7,10.1,7,10.1,6.9,10.1C7,10.1,7,10.1,6.9,10.1C6.9,10.1,6.9,10.1,6.9,10.1
C6.9,10.1,6.9,10.1,6.9,10.1L6.9,10.1L6.9,10.1L6.9,10.1C6.9,10.1,6.9,10.1,6.9,10.1L6.9,10.1L6.9,10.1L6.9,10.1L6.9,10.1
C6.9,10.1,6.9,10.1,6.9,10.1C6.9,10.1,6.9,10.1,6.9,10.1L6.9,10.1L6.9,10.1L6.9,10.1L6.9,10.1L6.9,10.1L6.9,10.1L6.9,10.1
C6.9,10.1,6.9,10.1,6.9,10.1L6.9,10.1C6.9,10.1,6.9,10.1,6.9,10.1 M6.6,10.1C6.7,10.1,6.7,10.1,6.6,10.1C6.7,10.1,6.7,10.1,6.6,10.1
C6.7,10.1,6.6,10.1,6.6,10.1L6.6,10.1C6.6,10.1,6.6,10.1,6.6,10.1L6.6,10.1C6.6,10.1,6.6,10.1,6.6,10.1L6.6,10.1
C6.6,10.1,6.6,10.1,6.6,10.1L6.6,10.1C6.6,10.1,6.6,10.1,6.6,10.1c-0.1,0-0.3,0.1-0.3,0.1c0,0,0,0,0,0c0,0,0,0-0.1,0
c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0-0.1,0-0.1,0c0,0,0.1,0,0.1,0C6.6,10.1,6.6,10.1,6.6,10.1 M6.8,10.1L6.8,10.1
C6.8,10.1,6.8,10.1,6.8,10.1C6.8,10.1,6.8,10.1,6.8,10.1L6.8,10.1L6.8,10.1L6.8,10.1L6.8,10.1L6.8,10.1L6.8,10.1
C6.8,10.1,6.8,10.1,6.8,10.1L6.8,10.1C6.8,10.1,6.8,10.1,6.8,10.1c-0.1,0-0.1,0-0.1,0c0,0,0,0,0,0C6.8,10.1,6.8,10.1,6.8,10.1
M7.1,10.1C7.1,10.1,7.1,10.1,7.1,10.1C7.1,10,7.1,10,7.1,10.1C7.1,10,7.1,10,7.1,10.1L7.1,10.1L7.1,10.1L7.1,10.1L7.1,10.1
L7.1,10.1L7.1,10.1L7.1,10.1L7.1,10.1C7.1,10,7.1,10,7.1,10.1C7.1,10.1,7.1,10.1,7.1,10.1C7,10.1,7,10.1,7.1,10.1
C7.1,10.1,7.1,10.1,7.1,10.1 M7,10.1c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
C7.1,10,7,10.1,7,10.1C7,10.1,7,10.1,7,10.1C7,10,7,10.1,7,10.1C7,10.1,7,10.1,7,10.1C7,10.1,7,10.1,7,10.1C7,10.1,7,10.1,7,10.1
C7,10.1,7,10.1,7,10.1C7,10.1,7,10.1,7,10.1 M7.3,10C7.3,10,7.4,10,7.3,10C7.4,10,7.3,10,7.3,10C7.3,10,7.4,10,7.3,10
C7.4,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10
C7.4,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10c0.1,0,0.1,0,0.1,0
C7.4,10,7.4,10,7.3,10c0.1,0,0.1,0,0.2,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0C7.5,10,7.4,10,7.3,10C7.4,10,7.4,10,7.3,10
C7.3,10,7.3,10,7.3,10L7.3,10C7.3,10,7.3,10,7.3,10C7.3,10,7.3,10,7.3,10C7.3,10,7.3,10,7.3,10c-0.1,0-0.1,0-0.2,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0C7.2,10.1,7.2,10.1,7.3,10C7.3,10,7.3,10,7.3,10C7.3,10.1,7.3,10,7.3,10
C7.3,10,7.3,10,7.3,10C7.3,10,7.3,10,7.3,10 M7.5,10C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10
C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10 M7.5,10L7.5,10L7.5,10C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10
C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10L7.5,10C7.5,10,7.5,10,7.5,10L7.5,10
C7.5,10,7.5,10,7.5,10L7.5,10C7.5,10,7.5,10,7.5,10L7.5,10C7.5,10,7.5,10,7.5,10c-0.1,0-0.1,0-0.1,0C7.4,10,7.4,10,7.5,10
C7.4,10,7.4,10,7.5,10L7.5,10L7.5,10L7.5,10L7.5,10L7.5,10L7.5,10L7.5,10c-0.1,0-0.1,0-0.1,0l0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0h0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0
c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0
C7.4,10.1,7.4,10.1,7.5,10C7.4,10.1,7.4,10.1,7.5,10C7.4,10.1,7.4,10.1,7.5,10C7.4,10.1,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10
C7.5,10,7.5,10,7.5,10L7.5,10C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10
C7.5,10,7.5,10,7.5,10C7.5,10,7.5,10,7.5,10 M9,11.2C8.6,10.5,8.1,10,7.5,10h0h0h0c0,0,0,0,0,0l0,0h0l0,0l0,0l0,0c0,0,0,0,0,0
c0,0,0,0,0,0l0,0l0,0l0,0l0,0l0,0c0,0,0,0,0,0v0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0h0c0,0,0,0,0.1,0c0,0,0,0,0.1,0
c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0l0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0
l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0h0c0,0,0,0,0,0h0c0,0,0,0,0,0h0h0c0,0,0,0,0,0h0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0h0h0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0h0l0,0l0,0h0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0h0c0,0,0,0,0.1,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c-0.1,0-0.1,0-0.2,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0l0,0c0,0,0,0,0,0
c0,0,0,0,0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0h0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0v0
l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0-0.1,0-0.1,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0v0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0-0.1,0-0.1,0l0,0c0,0,0.1,0,0.1,0l0,0l0,0
c0,0,0,0,0,0c-0.1,0-0.1,0-0.1,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0-0.1,0h0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
l0,0c0,0,0.1,0,0.1,0c0,0,0,0,0.1-0.1l0,0l0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0.1l0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0
c0,0,0,0,0.1,0c0,0,0,0,0,0.1l0,0c0,0,0,0,0-0.1l0,0l0,0c0,0,0,0,0,0h0l0,0c0,0,0,0,0-0.1l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0.1,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1-0.1-0.1,0l0,0l0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0l0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0.1,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0l0,0c0,0,0,0,0,0C8.9,11.1,8.9,11.1,9,11.2C8.9,11.1,8.9,11.1,9,11.2L9,11.2C8.9,11.2,8.9,11.2,9,11.2
C8.9,11.2,8.9,11.2,9,11.2C8.9,11.2,8.9,11.2,9,11.2C8.9,11.2,8.9,11.2,9,11.2C8.9,11.2,8.9,11.2,9,11.2C8.9,11.2,8.9,11.2,9,11.2
C8.9,11.2,8.9,11.2,9,11.2C8.9,11.2,8.9,11.2,9,11.2C8.9,11.2,8.9,11.2,9,11.2C8.9,11.2,9,11.2,9,11.2C9,11.2,9,11.2,9,11.2"/>
<path style="fill:#B3E710;" d="M7.4,22.9L7.4,22.9C7.4,22.9,7.4,22.9,7.4,22.9L7.4,22.9C7.4,22.9,7.4,22.9,7.4,22.9L7.4,22.9
C7.4,22.9,7.4,22.9,7.4,22.9C7.4,22.9,7.4,22.9,7.4,22.9 M9.4,20.1C9.4,20.1,9.4,20.1,9.4,20.1c-0.1,0-0.1,0-0.2,0c0,0,0,0.1,0,0.1
C9.2,20.2,9.3,20.2,9.4,20.1 M8.8,22.1c0.3-0.5,0.6-1.2,0.8-2c0,0,0,0,0,0c0,0,0,0-0.1,0c-0.1,0-0.1,0.1-0.2,0.2c0,0,0,0,0,0
c0,0-0.1,0-0.1,0c0,0,0-0.1,0-0.1c-0.1,0-0.1,0-0.1,0c0,0,0-0.1,0.1-0.1c-0.5,0-1.1,0-1.7,0c-0.5,0-1,0-1.4,0c0,0,0,0.1,0,0.1
c0,0,0,0.1,0,0.2l0,0c0.1,0.1,0.1,0,0.1,0c0.1,0.1-0.1,0.1-0.1,0.2c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0c0.1,0.1,0.2,0.1,0.6,0.7
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0.1,0.2,0.4,0.2,0.5,0.3
c0.1,0.1-0.1,0.3-0.1,0.4c0,0-0.1,0-0.1,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0c0,0,0,0,0,0s0,0,0,0l0.1,0
c0,0.1,0.1,0.7,0.1,0.7c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0l0,0l0,0l0,0c0,0,0,0,0,0l0,0
c0,0,0,0,0,0h0c0,0,0,0,0,0h0h0c0,0,0,0,0,0c0,0,0,0,0,0l0,0h0l0,0l0,0l0,0h0c0,0,0-0.1,0-0.1c0,0,0,0,0.1-0.1c0,0,0.1,0,0.2-0.1
c0,0,0,0,0-0.1c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0.1,0,0.2,0,0.4-0.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0-0.1,0.1-0.1,0.1c0.2-0.1,0.2-0.1,0.4-0.3c0,0,0,0,0,0C8.7,22.1,8.8,22.1,8.8,22.1 M13.7,18.3C13.7,18.3,13.7,18.3,13.7,18.3
C13.7,18.3,13.7,18.3,13.7,18.3C13.7,18.3,13.7,18.3,13.7,18.3C13.7,18.3,13.7,18.4,13.7,18.3L13.7,18.3
C13.7,18.4,13.7,18.3,13.7,18.3 M14,15.4c0-0.2-0.1-0.5-0.1-0.7v0c0-0.1-0.1-0.1-0.2-0.2c0,0-0.1,0-0.3-0.6c0,0,0,0,0,0
c0-0.1,0-0.1-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0-0.1,0c0,0,0,0-0.1,0.1c0,0,0,0,0,0.1c0,0,0,0.1-0.1,0.1
c0,0,0,0-0.1,0c-0.1,0-0.1,0-0.1-0.1c0,0-0.1,0.5,0,0.7c0.1,0.3-0.2,0.4-0.2,0.7c0,0.2-0.1,0.4-0.1,0.7c0,0,0,0,0,0
c0.1,0.1,0.1,0.7,0,0.8c0,0,0,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0.1,0,0.1c0,0,0,0,0,0l0,0l0,0l0,0l0,0h0l0,0l0,0l0-0.1
c0,0,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0,0.2,0,0.2c0,0,0.1,0.1,0.1,0.1c0,0,0.1,0.1,0.1,0.1v0c0,0-0.1,0-0.1,0c0,0,0,0,0.1,0.2
c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0v0v0L12.8,18c0,0,0,0,0,0c0,0,0.2,0.3,0.3,0.3
c0.1,0,0.1-0.2,0.2-0.2c0,0,0,0,0-0.1c0,0,0,0,0,0c0.1,0,0.3-0.2,0.3-0.2c0,0,0.2,0,0.2,0.1l0.2,0v0c0,0.1-0.2,0.1-0.2,0.1
c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0.1,0.1,0.1,0.1v0l-0.1,0c0,0.1-0.1,0.2-0.2,0.3l0,0c0,0,0,0.1,0,0.1c0,0,0,0,0,0
c0,0,0,0.1-0.1,0.2l0,0l0,0c0,0,0,0,0,0.1c-0.1,0.4-0.5,1-0.5,1c0,0,0,0,0,0s0,0,0,0c0.1,0,0.2-0.3,0.3-0.6c0.2-0.3,0.3-0.6,0.4-0.9
s0.2-0.6,0.2-1c0.1-0.3,0.1-0.7,0.1-1C14.1,16.1,14.1,15.8,14,15.4 M13.4,13.8C13.4,13.8,13.4,13.8,13.4,13.8
C13.4,13.8,13.4,13.8,13.4,13.8C13.4,13.8,13.4,13.8,13.4,13.8C13.4,13.8,13.4,13.8,13.4,13.8C13.4,13.8,13.4,13.8,13.4,13.8
c0,0.1,0,0.1,0.1,0.2C13.5,13.9,13.5,13.9,13.4,13.8 M13.2,13.5C13.2,13.5,13.2,13.5,13.2,13.5C13.2,13.4,13.2,13.4,13.2,13.5
C13.2,13.5,13.2,13.5,13.2,13.5C13.2,13.5,13.2,13.5,13.2,13.5C13.2,13.5,13.2,13.5,13.2,13.5c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0C13.3,13.6,13.3,13.6,13.2,13.5 M13.1,13.3C13.1,13.3,13.1,13.2,13.1,13.3
L13.1,13.3L13.1,13.3C13.1,13.3,13.1,13.3,13.1,13.3L13.1,13.3C13.1,13.3,13.1,13.3,13.1,13.3C13.1,13.3,13.1,13.3,13.1,13.3
C13.1,13.3,13.1,13.3,13.1,13.3C13.1,13.3,13.1,13.3,13.1,13.3L13.1,13.3L13.1,13.3L13.1,13.3L13.1,13.3L13.1,13.3L13.1,13.3
c0,0.1,0.1,0.1,0.1,0.1l0,0C13.2,13.4,13.1,13.3,13.1,13.3 M13.3,13.5C13.3,13.5,13.2,13.5,13.3,13.5l-0.1-0.1c0,0,0,0,0,0
c0,0,0,0,0,0c0,0-0.2-0.4-0.3-0.4l0,0c0,0-0.1,0-0.3,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0l0,0l0,0
c0,0,0.1,0.1,0.1,0.2c0,0,0,0-0.1,0c0,0.1-0.1,0-0.1,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0.1,0,0.1,0.1
c0.1,0.1,0.1,0.3,0.1,0.4c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1
c0,0,0,0.1,0,0.1c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0.1,0.1,0.1c0,0.1,0.1,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0l0,0h0
c0,0,0-0.1,0-0.1c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0-0.1,0-0.1c0,0,0,0,0,0
c0,0,0,0-0.1-0.1c0-0.1,0-0.1,0-0.2l0,0c0,0,0-0.1,0-0.1c0-0.1,0-0.1,0-0.1c-0.1-0.1-0.1-0.1-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0-0.1,0-0.1c0,0,0,0,0,0C13,13,13.3,13.5,13.3,13.5C13.3,13.5,13.3,13.5,13.3,13.5L13.3,13.5
L13.3,13.5L13.3,13.5L13.3,13.5C13.3,13.5,13.3,13.5,13.3,13.5C13.3,13.5,13.3,13.5,13.3,13.5C13.3,13.5,13.3,13.6,13.3,13.5
C13.3,13.6,13.3,13.6,13.3,13.5c0.1,0.1,0.1,0.2,0.1,0.2c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0.1c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0-0.1
C13.4,13.7,13.3,13.6,13.3,13.5 M12.9,12.9C12.9,12.9,12.9,12.9,12.9,12.9C12.9,12.9,12.9,12.9,12.9,12.9C12.9,12.9,13,13,12.9,12.9
C13,13,12.9,12.9,12.9,12.9 M13.1,13.1c0-0.1-0.1-0.1-0.1-0.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0C13,13,13,13,13.1,13.1
C13,13,13,13,13.1,13.1C13,13.1,13.1,13.1,13.1,13.1C13.1,13.1,13.1,13.1,13.1,13.1 M9.2,12.9L9.2,12.9L9.2,12.9L9.2,12.9L9.2,12.9
c-0.1,0-0.1,0-0.1,0.1C9.1,12.9,9.1,12.9,9.2,12.9C9.2,12.9,9.2,12.9,9.2,12.9C9.2,12.9,9.2,12.9,9.2,12.9 M2.7,12.5
C2.8,12.5,2.7,12.5,2.7,12.5C2.7,12.5,2.7,12.5,2.7,12.5C2.7,12.6,2.7,12.6,2.7,12.5C2.7,12.6,2.7,12.6,2.7,12.5 M2.9,12.4
C2.9,12.4,2.9,12.4,2.9,12.4C2.9,12.3,2.9,12.3,2.9,12.4c0.1-0.1,0.1-0.1,0.1-0.1c0,0,0,0,0,0C2.8,12.3,2.8,12.4,2.9,12.4
C2.8,12.4,2.8,12.4,2.9,12.4C2.8,12.4,2.8,12.4,2.9,12.4C2.8,12.4,2.8,12.4,2.9,12.4C2.8,12.4,2.8,12.4,2.9,12.4
C2.8,12.4,2.8,12.4,2.9,12.4C2.8,12.4,2.8,12.4,2.9,12.4C2.9,12.4,2.9,12.4,2.9,12.4 M2.9,12.2C3,12.1,3,12.1,2.9,12.2
C3,12.1,3,12.1,2.9,12.2C3,12.1,3,12.1,2.9,12.2C2.9,12.1,2.9,12.2,2.9,12.2C2.9,12.2,2.9,12.2,2.9,12.2L2.9,12.2
C2.9,12.2,2.9,12.2,2.9,12.2 M3,12.1C3,12,3,12,3,12.1C3.1,12,3.1,12,3,12.1C3.1,12,3.1,12,3,12.1C3,12,3,12,3,12.1
C3,12.1,3,12.1,3,12.1C3,12,3,12.1,3,12.1 M9,13c0,0,0-0.2,0-0.2c0,0,0.1,0,0-0.1c0-0.2,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0-0.1,0-0.1,0c-0.1,0-0.1,0.1-0.2,0.1c0,0,0-0.1,0-0.1c0.1,0,0.2-0.1,0.2-0.1c-0.3-0.1-0.3-0.2-0.3-0.2
c0-0.1,0-0.3-0.1-0.3c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0
c0,0,0,0.1-0.1,0c0,0,0,0.1,0,0.1c0,0,0.1,0,0.1,0c0-0.1-0.1,0-0.1-0.1c0,0,0,0,0,0C8.4,12,8.4,12,8.3,12c0,0,0,0,0,0c0,0,0,0,0,0
c-0.1-0.1-0.1-0.1-0.2-0.2c0,0,0,0-0.1,0.1c0,0,0,0,0,0.1C8,11.9,8,12,8,12c0,0,0,0,0,0c0,0,0-0.1-0.1-0.1C7.9,12,7.9,12,7.8,12
c0,0,0,0,0,0c0-0.1-0.1,0-0.2,0c0,0,0,0,0,0c0,0,0-0.1,0-0.1c0,0,0,0.2,0,0.2c0,0,0-0.1,0-0.1c0,0,0,0,0,0c-0.1,0-0.2-0.2-0.2-0.2
c0,0,0-0.1,0-0.1c0,0-0.1-0.1-0.1-0.1c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.2-0.1c0,0,0,0,0,0c0,0-0.1,0,0,0.1c0,0,0,0.1,0,0.1v0
c0,0,0,0,0,0c0,0,0,0.1,0,0.1l-0.1,0.1C6.9,12,7.1,12,7,12.2c0,0.1-0.1,0.4-0.3,0.5c0,0,0.1,0.3,0.1,0.3c0,0,0,0,0,0
c0,0,0.3-0.4,0.3-0.4c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0.1-0.1,0.1-0.1,0.2c0,0-0.1,0.1-0.1,0.1c0,0-0.1,0-0.1-0.1c0,0,0,0-0.1,0c0,0,0,0.1,0,0.1c0.3,0,0.5,0,0.8,0c0.5,0,1,0,1.5,0
c0,0-0.1-0.1-0.1-0.1C9,12.9,9,12.9,9,12.8 M9,11.3C9,11.2,9,11.2,9,11.3L9,11.3L9,11.3 M6.8,11.3L6.8,11.3c0,0,0,0-0.1,0
c0,0,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c-0.1,0.1-0.1,0.1-0.1,0.2c-0.1,0-0.1,0-0.1,0.1c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0.1,0
c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0C6.7,11.3,6.7,11.3,6.8,11.3C6.8,11.3,6.8,11.3,6.8,11.3 M7.3,10.8
C7.3,10.8,7.3,10.8,7.3,10.8C7.3,10.8,7.2,10.8,7.3,10.8c-0.2,0-0.2,0.1-0.1,0.1C7.1,10.9,7.2,11,7.3,10.8"/>
<path style="fill:#B3E710;" d="M9.6,13.4C9.6,13.4,9.6,13.4,9.6,13.4C9.6,13.4,9.6,13.4,9.6,13.4C9.6,13.4,9.6,13.3,9.6,13.4
L9.6,13.4C9.5,13.3,9.5,13.3,9.6,13.4C9.5,13.4,9.5,13.4,9.6,13.4c-0.1-0.1-0.1-0.1-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0
l0,0c0,0,0,0-0.1,0c0,0,0,0,0.1-0.1c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0-0.1,0c0,0,0,0-0.1,0.1c0-0.1,0-0.1,0.1-0.2c0,0-0.1,0-0.1,0C9,13,9,13,9,13.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0
c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0-0.1,0-0.1,0.1c0,0,0,0,0,0.1c0.1,0,0.1,0,0.2,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0
c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0l0,0c0,0,0,0,0,0.1
c0,0,0,0,0,0l0,0L9.6,13.4C9.5,13.4,9.5,13.5,9.6,13.4C9.6,13.5,9.6,13.5,9.6,13.4C9.6,13.5,9.6,13.4,9.6,13.4 M12.6,12.9
C12.6,12.9,12.6,12.9,12.6,12.9C12.6,12.9,12.6,12.9,12.6,12.9L12.6,12.9C12.6,12.9,12.6,12.9,12.6,12.9 M11.6,11.8
c0,0-0.1-0.2-0.2-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0C11.7,11.9,11.7,11.9,11.6,11.8C11.6,11.9,11.6,11.9,11.6,11.8
M11.5,11.7L11.5,11.7C11.5,11.7,11.4,11.7,11.5,11.7C11.4,11.7,11.4,11.7,11.5,11.7C11.4,11.7,11.5,11.7,11.5,11.7 M11.8,11.7
C11.8,11.6,11.8,11.6,11.8,11.7L11.8,11.7C11.8,11.7,11.8,11.7,11.8,11.7C11.8,11.7,11.8,11.7,11.8,11.7
C11.8,11.7,11.8,11.7,11.8,11.7C11.9,11.7,11.9,11.7,11.8,11.7C11.9,11.7,11.9,11.7,11.8,11.7C11.9,11.7,11.9,11.7,11.8,11.7
C11.9,11.7,11.9,11.7,11.8,11.7L11.8,11.7L11.8,11.7C11.9,11.7,11.8,11.7,11.8,11.7 M11.6,11.4C11.6,11.4,11.6,11.4,11.6,11.4
L11.6,11.4C11.6,11.5,11.6,11.5,11.6,11.4C11.6,11.5,11.6,11.5,11.6,11.4c0.1,0.1,0.1,0.1,0.1,0.1C11.7,11.5,11.7,11.5,11.6,11.4
C11.6,11.5,11.6,11.5,11.6,11.4C11.6,11.5,11.6,11.5,11.6,11.4 M9.4,11.4C9.4,11.4,9.4,11.4,9.4,11.4C9.4,11.4,9.4,11.4,9.4,11.4
C9.4,11.4,9.4,11.4,9.4,11.4C9.4,11.4,9.3,11.4,9.4,11.4C9.3,11.4,9.4,11.4,9.4,11.4C9.4,11.4,9.4,11.4,9.4,11.4 M12.1,12.1
c0,0-0.1-0.1-0.1-0.1C12,12,12,12,12.1,12.1C11.9,12,11.9,12,11.9,12c0,0-0.1-0.1-0.2-0.2c-0.2-0.1-0.2-0.2-0.2-0.2c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1-0.1-0.1-0.1c0,0-0.1,0-0.1,0c0,0,0-0.1-0.1-0.1c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0.1c0,0,0.1,0.1,0.1,0.1l0,0
c0,0,0,0,0,0l0,0c0,0,0,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0.1,0.1,0.1,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0
c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0.1l0,0C11.9,12.1,11.9,12.1,12.1,12.1C12,12.2,12,12.2,12.1,12.1C12,12.2,12,12.2,12.1,12.1
C12,12.2,12,12.1,12.1,12.1C12,12.2,12,12.2,12,12.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0.1,0,0.1,0,0.1
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0C12.1,12.2,12.1,12.2,12.1,12.1
C12.1,12.2,12.1,12.2,12.1,12.1C12.1,12.2,12.1,12.2,12.1,12.1C12.1,12.2,12.1,12.2,12.1,12.1C12.1,12.1,12.1,12.1,12.1,12.1
M9.9,10.7c0,0-0.1,0-0.1,0c0,0,0,0-0.1,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0
c0,0-0.1,0-0.1,0c0,0,0,0,0,0l0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0-0.1-0.1-0.2-0.1c0,0,0,0,0,0l0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0.1,0l0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0.1,0,0.1,0,0.1,0.1c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0
c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0C10,10.8,10,10.8,9.9,10.7C10,10.8,10,10.8,9.9,10.7C10,10.8,10,10.7,9.9,10.7
C10,10.7,10,10.7,9.9,10.7C10,10.7,9.9,10.7,9.9,10.7 M8.6,10.2C8.6,10.2,8.6,10.2,8.6,10.2C8.5,10.2,8.5,10.2,8.6,10.2
C8.6,10.2,8.6,10.2,8.6,10.2L8.6,10.2C8.7,10.2,8.6,10.2,8.6,10.2L8.6,10.2L8.6,10.2L8.6,10.2L8.6,10.2C8.6,10.2,8.6,10.2,8.6,10.2
M12.1,11.9C12.1,11.9,12,11.8,12.1,11.9L12.1,11.9C12,11.9,12.1,11.9,12.1,11.9c0,0-0.1-0.1-0.1-0.1l0,0l0,0h0
C12,11.8,12,11.9,12.1,11.9C12.1,11.9,12.1,11.9,12.1,11.9L12.1,11.9L12.1,11.9L12.1,11.9C12,11.8,12,11.8,12,11.8c0,0,0,0,0,0
c0,0-0.1-0.1-0.1-0.1l0,0c0,0,0,0-0.1-0.1l0,0l0,0l0,0c0,0,0,0-0.1,0c0,0,0,0,0.1,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0.1,0.1,0.1,0.1l0,0c0.1,0,0.1,0.1,0.2,0.2l0,0L12.1,11.9L12.1,11.9L12.1,11.9C12,11.9,12,11.9,12.1,11.9
C12,11.9,12,11.9,12.1,11.9C12.1,11.9,12,11.9,12.1,11.9C12.1,11.9,12.1,11.9,12.1,11.9L12.1,11.9L12.1,11.9L12.1,11.9
C12.1,11.9,12,11.9,12.1,11.9C12,11.9,12,11.9,12.1,11.9C12,11.9,12,11.9,12.1,11.9C12.1,11.9,12.1,11.9,12.1,11.9
C12.1,12,12.1,12,12.1,11.9L12.1,11.9C12,11.9,12,11.9,12.1,11.9C12,11.9,12,11.9,12.1,11.9C12,12,12.1,12,12.1,11.9
c0,0.1,0,0.1,0,0.2c0,0,0,0,0,0c0,0,0,0,0,0l0,0C12.1,12,12.1,12,12.1,11.9c0,0.1,0,0.1,0.1,0.3c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0.1l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0l0,0l0,0
c0,0,0,0,0.1,0.1c0.1,0.1,0.1,0.1,0.2,0.2c0.2,0,0.3,0,0.3,0l0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c-0.1-0.1-0.1-0.2-0.2-0.3C12.6,12.4,12.3,12.1,12.1,11.9 M10.5,10.7c-0.2-0.1-0.4-0.2-0.6-0.3l0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0.1C10.4,10.7,10.5,10.7,10.5,10.7C10.5,10.8,10.5,10.8,10.5,10.7
c0.1,0.1,0.2,0.1,0.2,0.1l0,0c0,0,0.1,0,0.1,0.1c0,0,0,0,0,0l0,0h0l0,0h0c0,0,0,0,0,0l0,0h0l0,0l0,0c0,0,0,0,0.1,0c0,0,0,0,0,0
c0,0,0,0,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0.1l0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0.1,0.1,0.1,0.1l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0
c-0.1-0.1-0.1-0.1-0.2-0.2c0,0,0,0,0.1,0c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0l0,0c0,0,0.1,0.1,0.1,0.1c0,0,0.1,0.1,0.1,0.1l0,0l0,0l0,0
c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0-0.1-0.1l0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0-0.1-0.1-0.1-0.1c-0.1-0.1-0.2-0.2-0.3-0.3
C11.1,11.1,10.8,10.9,10.5,10.7 M8.3,10.1L8.3,10.1C8.3,10.1,8.3,10.1,8.3,10.1L8.3,10.1L8.3,10.1C8.3,10.1,8.2,10.1,8.3,10.1
L8.3,10.1C8.2,10.1,8.3,10.1,8.3,10.1L8.3,10.1L8.3,10.1C8.3,10.1,8.3,10.1,8.3,10.1L8.3,10.1L8.3,10.1L8.3,10.1L8.3,10.1
C8.3,10.1,8.3,10.1,8.3,10.1C8.3,10.1,8.3,10.1,8.3,10.1L8.3,10.1C8.3,10.1,8.3,10.1,8.3,10.1 M8.2,10.1L8.2,10.1L8.2,10.1
C8.2,10.1,8.2,10.1,8.2,10.1C8.2,10.1,8.2,10.1,8.2,10.1C8.2,10.1,8.2,10.1,8.2,10.1C8.2,10.1,8.2,10.1,8.2,10.1L8.2,10.1
C8.2,10.1,8.2,10.1,8.2,10.1C8.2,10.1,8.2,10.1,8.2,10.1 M8.2,10.1C8.2,10.1,8.2,10.1,8.2,10.1L8.2,10.1L8.2,10.1L8.2,10.1
C8.1,10.1,8.1,10.1,8.2,10.1L8.2,10.1L8.2,10.1L8.2,10.1L8.2,10.1C8.2,10.1,8.2,10.1,8.2,10.1C8.2,10.1,8.2,10.1,8.2,10.1 M8,10
L8,10C7.9,10,7.9,10,8,10C7.9,10,8,10,8,10C8,10,8,10,8,10 M7.9,10C7.8,10,7.8,10,7.9,10C7.8,10,7.8,10,7.9,10L7.9,10
C7.8,10,7.9,10,7.9,10C7.9,10,7.9,10,7.9,10 M7.8,10L7.8,10C7.8,10,7.7,10,7.8,10C7.8,10,7.8,10,7.8,10C7.7,10,7.7,10,7.8,10
C7.7,10,7.7,10,7.8,10C7.8,10,7.8,10,7.8,10L7.8,10C7.8,10,7.8,10,7.8,10 M7.7,10L7.7,10L7.7,10C7.6,10,7.6,10,7.7,10
C7.6,10,7.6,10,7.7,10C7.6,10,7.6,10,7.7,10C7.6,10,7.7,10,7.7,10L7.7,10 M7.9,10c-0.1,0-0.2,0-0.2,0h0c0,0,0.1,0,0.1,0
C7.8,10,7.8,10,7.9,10 M7.6,10C7.6,10,7.5,10,7.6,10L7.6,10C7.6,10,7.6,10,7.6,10C7.6,10,7.6,10,7.6,10 M8,10L8,10L8,10L8,10L8,10
M9,10H8.7c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0.2,0,0.2,0.1C9,10.2,9,10,9,10H8.7l0,0.1c0,0,0,0,0,0
l0-0.1c0,0,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0.2,0.1,0.2,0.1c0,0,0.2,0.1,0.2,0.1v0c0,0-0.2-0.1-0.2-0.1c0,0-0.2-0.1-0.2-0.1h0
c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0C8.5,10,9,10.1,9,10v0.1c-1,0-0.3,0-0.3,0c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c0,0,0,0,0,0
c0,0,0.1,0,0.1,0l0,0h0l0,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0.1,0l0,0c0,0,0-0.1,0-0.1l0-0.1h0.2
C8.8,10,9,10.1,9,10L9,10H8.3c0,0-0.3,0.1-0.3,0.1v-0.1c0,0,0.5-0.1,0.5-0.1h0l0,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0-0.4,0-0.4,0v0.1l0,0l0,0v0l0,0l0,0l0,0l0.4,0h0l0,0h0c0,0-0.4,0-0.4,0l0,0v0.1v0l0,0v0v0l0,0c0,0,0.2-0.1,0.2-0.1
c0,0,0.2,0,0.2,0h0c0,0,0,0.1,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0-0.1,0h0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1,0V10h0.1l0,0l0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0-0.2,0-0.2,0v0v0l0,0v0l0,0v0v0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0v0v0v0h0h0c0,0,0,0,0,0h0
c0,0,0,0,0,0h0v0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0h0c0,0,0,0,0,0h0c0,0,0,0,0,0h0c0,0,0,0,0,0c0,0-0.1,0-0.1,0h0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0h0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0h0h0c0,0,0,0,0,0c0,0,0,0,0,0
c0.5,0,1,0.5,1.4,1.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0-0.2,0-0.1c0,0,0-0.2,0-0.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.2,0,0.2
c0,0,0,0.1,0,0.1c0,0,0.1,0,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0
c0,0,0,0,0,0l0,0l0,0h0c0,0,0,0,0,0h0c0,0,0,0,0,0c0,0-0.2,0-0.2,0c0,0-0.2,0-0.2,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0.2,0,0.2,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0l0,0c0,0-0.2,0-0.2,0c0,0-0.2,0-0.2,0c0,0,0-0.2,0-0.2
c0,0,0.5-0.2,0.5-0.2h0c0,0,0,0.2,0,0.2c0,0,0,0.1,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0.2,0,0.2,0c0,0,0,0,0,0c0,0-0.1,0-0.2,0c0,0,0.1,0,0,0
c0,0,0.1,0,0.1,0c0,0,0-0.4,0-0.4s-0.3,0-0.3,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.2,0,0.2c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1h0l0,0.2
c0,0,0,0.1,0,0.1c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1-0.1
c0,0,0,0,0.1,0l0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0.2,0,0.2,0c0,0-0.7,0,0.3,0v0c-1,0-0.3,0-0.3,0H9.6c0,0,0,0,0,0
c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0-0.5,0-0.5,0v0l0,0v0l0,0v0h0.2H9v0l0.3,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0L9,11v0
c0,0,0.4,0,0.4,0c0,0,0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0l0,0c0,0,0,0-0.1,0l0,0l0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0-0.1,0c0,0,0,0,0.1,0c0,0,0,0.1,0,0.1c0,0,0,0.1-0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.3-0.1-0.3-0.1v-0.1c0,0,0.1,0,0.1-0.1c0,0,0.1,0,0.1,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0-0.1,0-0.1,0c0,0-0.2,0-0.2,0c0,0,0,0,0,0c0,0,0.2,0.3,0.2,0.3h0c0,0,0-0.2,0-0.2c0,0,0.1,0.1,0.1,0.1c0,0,0,0.1,0,0.1
c0,0,0,0,0,0c0,0,0-0.2,0-0.2c0,0,0-0.1,0-0.1c0,0-0.1,0-0.1,0c0,0-0.2,0-0.2,0V11c0,0,0.2,0,0.2,0c0,0,0.1-0.2,0-0.2
c0,0,0-0.1,0-0.1c0,0,0-0.1,0.1,0c0,0,0,0.2,0,0.2l0,0.2c0,0,0,0,0,0c0,0,0-0.3,0-0.3l0-0.1l0-0.1c0,0,0-0.3,0-0.3C9,10.2,9,10,9,10
C9,10,9,10,9,10L9,10.2c0,0,0,0.1-0.1,0.1l0,0c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0l0,0l0,0c0,0-0.1,0-0.1,0c0,0,0.1,0,0.1,0c0,0,0-0.1,0-0.1V10H8.8H9v0.1
l0,0V10c0,0-0.1,0-0.1,0l0,0H9L9,10v0.2l-0.1,0c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1"/>
<path style="fill:#B3E710;" d="M10.4,20.8c0,0,0.1-0.2,0.1-0.2c0,0-0.1,0-0.1,0c-0.1-0.2-0.1-0.2-0.1-0.2c0,0-0.1,0-0.1,0
c0,0-0.1-0.1-0.1-0.1c0,0-0.1,0-0.2,0.1c0,0,0.1-0.1,0-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1-0.1c-0.2,0.8-0.5,1.5-0.8,2
c0.1,0,0.1,0,0.1,0c0,0,0.1,0,0.1-0.1c0,0,0.1,0,0.1,0c0,0,0.1-0.1,0.1-0.1c0-0.1,0.2-0.1,0.2-0.2c0.1-0.1,0.1-0.1,0.2-0.2
c0-0.1,0.1-0.1,0.2-0.3c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1-0.1c0,0,0.1-0.1,0.1-0.1c0.1-0.1,0.1-0.1,0.2-0.1
C10.3,20.8,10.4,20.8,10.4,20.8 M12.8,20.3c0.1-0.1,0.1-0.2,0.2-0.3c0,0,0,0,0,0s0,0,0,0c0,0,0,0,0,0c-0.6,0.9-1.1,1.3-1.3,1.5
c0.2-0.2,0.4-0.3,0.6-0.5C12.3,20.9,12.6,20.6,12.8,20.3 M9.5,20.1C9.5,20.1,9.5,20.1,9.5,20.1C9.5,20.1,9.5,20.1,9.5,20.1
C9.5,20.1,9.5,20.1,9.5,20.1 M9.4,20C9.4,20,9.4,20,9.4,20c0,0-0.1,0-0.2,0c0,0,0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0s0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1C9.3,20.1,9.3,20.1,9.4,20C9.4,20.1,9.4,20.1,9.4,20 M8.1,18.8C8.1,18.8,8.1,18.8,8.1,18.8
C8.1,18.8,8.1,18.8,8.1,18.8C8.1,18.8,8.1,18.8,8.1,18.8C8.1,18.8,8.1,18.8,8.1,18.8C8.1,18.8,8.1,18.9,8.1,18.8
C8.1,18.9,8.1,18.9,8.1,18.8C8.1,18.9,8,18.9,8,18.9c0,0,0,0,0,0C8.1,18.9,8.1,18.9,8.1,18.8C8.1,18.9,8.1,18.9,8.1,18.8 M6.8,17.4
L6.8,17.4c0-0.1-0.2-0.1-0.2-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0h0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0h0l0,0
c0,0,0.1,0.1,0.1,0.1C6.7,17.5,6.7,17.4,6.8,17.4C6.8,17.4,6.8,17.4,6.8,17.4C6.8,17.4,6.8,17.4,6.8,17.4
C6.8,17.4,6.8,17.4,6.8,17.4L6.8,17.4 M8.2,17.4C8.2,17.4,8.2,17.4,8.2,17.4c0,0-0.1-0.1-0.2,0c0,0,0,0,0,0.1
C8.1,17.4,8.2,17.4,8.2,17.4C8.2,17.4,8.2,17.4,8.2,17.4 M7.9,17.4C7.9,17.4,7.9,17.4,7.9,17.4c-0.1-0.1-0.2-0.1-0.2-0.1
c-0.1,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0s0,0-0.1,0c0,0,0,0-0.1,0c-0.1,0-0.1,0-0.2,0l0,0c0,0,0,0,0,0
c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c-0.1,0-0.2,0-0.3,0c0,0,0,0,0,0l0,0c0,0,0,0,0.1,0.1c0,0,0,0,0,0
c0,0,0,0,0.1,0c0.1,0,0.1,0,0.2,0c0,0,0,0.1,0.1,0.1c0-0.1,0-0.1,0.1-0.1c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0
C7.8,17.4,7.8,17.4,7.9,17.4C7.8,17.4,7.8,17.4,7.9,17.4C7.9,17.4,7.9,17.4,7.9,17.4 M7.1,17.1L7.1,17.1C7,17.1,7,17.1,6.9,17.1
c0,0,0,0,0,0c0,0,0,0,0,0h0h0h0h0h0h0l0,0l0,0c-0.1,0-0.2,0-0.2-0.1c0,0,0-0.1-0.1-0.1c0,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.2-0.1
c0,0,0,0,0,0c-0.1,0-0.1,0-0.2,0c0,0,0,0,0,0h0c0,0,0,0,0,0c0,0,0,0,0,0h0c0,0,0,0,0,0h0c0,0,0,0,0,0c-0.1,0-0.3,0-0.3,0.1
c0,0,0,0,0,0l0,0c0,0,0.1,0,0.1,0c0.2-0.1,0.2-0.1,0.3-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0.1,0,0.1,0,0.2,0c0.1,0,0.1,0,0.1,0.1c0,0,0,0,0,0c0.1,0,0.1,0,0.1,0.1c0.1,0,0.1,0,0.2,0.1c0,0,0,0.1-0.1,0.1
c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0.1,0,0.1,0,0.2,0C7,17.2,7.1,17.2,7.1,17.1 M7,15L7,15C7,15,6.9,14.9,7,15C6.9,14.9,6.9,14.9,7,15
C6.9,15,7,15,7,15 M6.9,14.9C6.9,14.9,6.9,14.9,6.9,14.9L6.9,14.9L6.9,14.9 M6.9,14.9C6.9,14.9,6.9,14.9,6.9,14.9L6.9,14.9
C6.9,14.9,6.9,14.9,6.9,14.9 M6.9,14.9C6.9,14.9,6.9,14.9,6.9,14.9L6.9,14.9C6.9,14.9,6.9,14.9,6.9,14.9 M6.9,14.9
C6.9,14.9,6.9,14.9,6.9,14.9L6.9,14.9C6.9,14.9,6.9,14.9,6.9,14.9 M8.9,13.6C8.9,13.6,8.9,13.6,8.9,13.6C8.9,13.6,8.8,13.5,8.9,13.6
c-0.1,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0-0.1,0-0.1c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0.1-0.1,0.1c0,0,0,0,0,0.1
C8.7,13.6,8.8,13.6,8.9,13.6C8.8,13.6,8.9,13.6,8.9,13.6 M2.7,13.3L2.7,13.3C2.7,13.2,2.7,13.2,2.7,13.3C2.7,13.2,2.7,13.2,2.7,13.3
c0-0.1,0-0.1,0-0.1c0,0,0-0.1,0-0.1c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0l0,0c0,0,0,0.1,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1l0,0
c0,0,0,0,0,0C2.6,13.2,2.6,13.2,2.7,13.3C2.6,13.2,2.6,13.2,2.7,13.3C2.7,13.2,2.6,13.2,2.7,13.3C2.6,13.2,2.6,13.2,2.7,13.3
C2.6,13.2,2.7,13.3,2.7,13.3C2.7,13.3,2.7,13.3,2.7,13.3C2.7,13.3,2.7,13.3,2.7,13.3 M9.3,19.8C9.3,19.8,9.3,19.8,9.3,19.8
c-0.1-0.1-0.1-0.1-0.1-0.3c0,0-0.4-0.3-0.5-0.2c0,0-0.1,0-0.1,0c0,0,0,0,0,0l0,0c-0.1-0.1-0.3-0.1-0.4-0.1c0,0-0.3,0-0.3,0
c0,0,0,0,0,0c0-0.2,0.1-0.2-0.1-0.2c0,0,0.1,0,0.1,0C8,19,8.1,19,8.1,19c-0.1,0,0,0-0.1-0.1c0,0,0,0-0.1,0c0,0,0,0,0,0
c0,0,0,0,0-0.1c0,0,0,0,0,0c0,0,0.1,0,0.1,0l0,0l0,0c-0.2,0-0.3,0-0.5,0c-0.1,0-0.3-0.1-0.4-0.1c0,0,0-0.1,0-0.1c0,0,0,0,0,0
c0,0,0,0,0,0l0,0l0,0c0,0,0,0-0.2,0.1c0,0,0,0,0,0c0,0.1,0.1,0.1,0.1,0.2c0,0,0,0-0.1,0c-0.1-0.1-0.1-0.1,0-0.2c0-0.1,0-0.1,0-0.1
c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0-0.3,0.3-0.3,0.3c0,0,0,0,0,0c0,0,0-0.3-0.2-0.4
c-0.1,0-0.2,0-0.3,0C6,18.5,5.8,18.2,5.9,18c0,0,0-0.1,0-0.2c0,0-0.1,0-0.1,0c0-0.1-0.2,0-0.2-0.1c-0.1,0-0.3,0-0.3,0c0,0,0,0,0,0
l0,0l0,0c0.1-0.1,0.1-0.1,0.1-0.3c0,0,0,0,0-0.1l0,0c0,0,0,0,0-0.1l0,0c0,0,0,0,0,0.1c0-0.1,0-0.1,0-0.2c0,0,0,0,0,0l0,0l0-0.1
C5.5,17,5.5,17,5.5,17c-0.2-0.1-0.3,0-0.4,0c-0.1,0,0,0-0.1,0c0,0.1,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0.2,0,0.2c0,0,0,0.1,0,0.1
c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0-0.3,0-0.4,0C4.2,17,4.2,17,4.2,16.9c0-0.3,0.1-0.8,0.1-0.8l0-0.1c0,0,0,0,0,0c0,0,0.1,0,0.1,0
c0,0,0,0,0,0c0.1,0,0.1,0,0.2-0.1c0,0,0,0,0-0.1l0,0.1c0.1,0,0.2,0.1,0.3,0.1c0,0,0,0,0,0l0-0.1l0,0l0,0c0,0,0.1,0.1,0.1,0.1
C5,16,5.1,16,5.1,16c0,0,0,0,0,0L5,15.9c0,0,0,0,0,0l0.1,0l0,0l0,0c0,0,0.1,0.1,0.1,0.1c0,0,0,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0-0.1,0
c0,0,0,0,0,0c0,0,0,0,0.1,0c0.1,0,0.1,0,0.2,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0.1,0.1c0,0,0,0,0,0c0.1,0,0.1,0,0.1,0
c0.2,0.1,0.2,0.1,0.2,0.1c0,0,0,0,0.2,0.1C6,16,6,16,6,16.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.1,0,0.1
c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0.1,0.1,0.1,0.1,0.2c0,0,0.1-0.3,0.1-0.3c0,0,0-0.2,0-0.2c0,0,0,0,0,0c0.1,0-0.2-0.3-0.1-0.6
c0.1-0.2,0.4-0.2,0.6-0.4l0,0.1l0,0l0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0.1,0,0.1,0
c0,0,0.1,0,0.1,0v0l-0.1,0l0,0l0,0l0.1,0l0,0v0l0,0c0,0,0,0.1,0,0.1c0,0,0,0,0,0c0,0,0-0.1,0-0.1c0,0,0,0,0,0l0,0l0,0l0,0.1
c0,0,0,0,0,0c0,0,0-0.1,0-0.1c0,0-0.1-0.1-0.1-0.1l0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0,0,0,0,0l0,0
l0,0c0,0,0,0,0,0l0-0.1c0,0,0-0.1-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0-0.1-0.1c0,0,0.1,0,0.1,0l0.1,0c0,0,0,0.4,0,0.4
s-0.2,0-0.2,0c0,0,0-0.2,0-0.2c0,0,0-0.1,0-0.1c0,0,0,0.2,0.1,0.2c0,0,0,0.1,0,0.1c0,0,0,0,0,0h0c0,0,0,0,0,0c0,0,0-0.3,0-0.3
c0,0,0-0.1,0-0.1l0,0.2l0,0.2c0,0,0,0,0,0l0-0.3l0-0.1l0-0.1l0,0.2l0,0.3h0l0-0.2l0-0.2c0,0,0,0.5,0,0.5s-0.1,0-0.1,0l0-0.2l0.1-0.1
l0-0.1c0,0,0,0,0,0l0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0c0,0,0,0.3,0,0.3s0,0,0,0
c0,0,0,0,0,0c0,0,0-0.1,0,0l0-0.1c0,0,0-0.1,0-0.1C7,14.7,7,14.4,7,14.3c0,0,0-0.3,0-0.3h0l0,0.5C7,14.5,7,15,7,15c0,0-0.1,0-0.1,0
s0-0.4,0-0.4l0,0c0,0,0.1-0.1,0.1-0.1c0-0.1,0.1-0.1,0-0.2l0,0c0,0,0,0,0,0c0.1,0,0.2-0.1,0.3-0.1c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.2,0,0.2,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0L7.7,14
c0,0,0.3,0,0.3,0s0,0.2,0,0.2l-0.2,0l-0.1,0c0,0-0.1-0.1-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0-0.1,0.2-0.1,0.2-0.1
c0,0,0,0,0,0l-0.1,0l-0.1,0.1L7.7,14c0,0,0,0,0,0l0-0.1l0,0l0,0l0,0l0,0c0,0,0,0.1,0,0c0,0,0,0.1,0,0.1h0l0-0.1l0,0l0,0l0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.1,0.1,0.1c0,0,0,0.1,0,0.1c0,0,0,0,0,0l0-0.1l0-0.1l0,0c0,0,0.1,0,0.1,0
c0,0,0,0,0,0l0,0c0,0,0.1,0,0.1-0.1l0,0.2l0,0.2c0,0,0,0,0,0c0,0,0-0.2-0.1-0.1c0,0,0-0.1,0-0.1c0.1,0,0.2-0.1,0.2-0.1
c0,0-0.1,0-0.1,0c0,0-0.2,0-0.2,0l-0.2,0V14h0.2l0,0l0,0c0,0,0.1,0,0.1,0l0,0l0,0l0,0l0,0c0,0,0,0-0.1,0c0,0,0,0,0,0L8,13.8
c0,0,0,0,0,0l0.1,0c0,0,0,0,0,0l0,0l0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0
c0,0,0.1-0.1,0.1-0.1c0,0,0,0.1,0,0.1l0,0.1c0,0,0,0,0,0l-0.2-0.1L8,13.8v0c0,0,0.3,0,0.3,0l0.1,0l0.1,0l0,0c0.1,0,0.1,0,0.2-0.1
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0l0,0l0,0c0,0,0,0-0.1,0l0,0l0,0l0,0c0,0-0.1,0-0.1,0c0,0,0,0.2-0.1,0.2c0,0,0,0.2,0,0.2
c0,0,0,0,0,0l0-0.2c0,0-0.1-0.1-0.1-0.1l0,0.2c0,0,0,0.1,0,0.1c0,0,0,0,0,0S8,14,8,14s0-0.4,0-0.4l0.2,0l-0.1,0
c-0.1,0-0.2,0-0.2-0.1c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0.1-0.1,0.1-0.1l-0.1,0l-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0-0.1,0.3-0.1,0.3-0.1c-0.2-0.2-0.6,0.1-0.7,0.3C7.8,13,8.2,13,8.6,13c0.1,0,0.2,0,0.3,0c-0.5,0-1,0-1.5,0c-0.3,0-0.5,0-0.8,0
c-1.7,0-3.1,0-3.9,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.3-0.1,0.4c0,0,0,0,0,0c0,0,0,0,0-0.1c0,0,0,0,0,0
c0,0-0.1,0-0.1,0c0,0,0,0.1,0,0.1c0,0,0,0,0,0l0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0.1-0.1,0.2-0.2,0.4c0,0.1,0,0.1-0.2,0.6c0,0,0,0,0,0c0,0,0,0,0,0l0,0.1l0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.1,0,0.1c0,0,0,0,0,0l0,0c0,0.1,0.1,0.3,0,0.4c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0l0,0
c0,0,0.3,0,0.3,0c0,0.1-0.7,0.1,0.3,0.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c-1,0-1,0-1,0c0,0,0,0,0,0
c0,0,0,0,0,0c1,0,0.4,0.6,0.4,0.6c0,0,0.1,0,0.1,0c0,0,0.3,0.5,0.5,0.7c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0-0.1c0,0,0,0,0,0
c0-0.1-0.1-0.4-0.1-0.4c0,0,0,0,0,0c0,0,0-0.1,0-0.1c0,0,0,0,0,0c0-0.2-0.1-0.3-0.2-0.5c0,0,0-0.1,0-0.1c0,0,0.1,0,0.1,0l0,0
c0,0,0,0,0,0c0,0,0,0,0,0C2.8,16,2.8,16,3.1,16.3c0,0.1,0,0.1,0,0.1l0,0c0.1,0.2,0.2,0.3,0.3,0.5c0,0,0,0,0,0c0,0.1,0,0.1,0,0.2
c0,0,0,0,0,0c0,0,0,0,0,0C3.5,17.3,4,17.6,4,17.6c0.1,0,0.2,0.1,0.3,0.1c0.1,0,0.2-0.1,0.4,0c0.2,0.1,0.2,0.2,0.4,0.3L5.4,18
c0,0,0,0,0-0.1c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0.1,0.1,0.2,0.2,0.3,0.2l0,0l0,0l0,0c0,0,0,0,0,0c0,0,0,0.1,0.1,0.1l0.1,0
c0,0,0.1,0,0.1-0.1c0,0,0,0,0,0c0,0,0.1,0.1,0.1,0.1c0,0-0.1,0-0.1,0c0,0,0,0,0,0l-0.1,0v0.2C6,18.8,6,19,6,19c0,0,0,0,0,0
c0.1,0,0.1,0,0.2,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0-0.1,0-0.1c0,0,0.1-0.1,0.1-0.1c0,0-0.2-0.1-0.2-0.1c0,0-0.2-0.1-0.2-0.1
c0,0,0,0,0,0c0,0,0.2,0,0.3,0c0,0,0.1,0,0.1,0c0.1,0,0.2,0,0.2,0.1c0,0,0.1,0.2,0.1,0.2c0,0,0,0.2,0.1,0.2c0,0,0,0,0,0
c0,0,0-0.1,0-0.1c0,0-0.1-0.1-0.2-0.1c0,0,0,0,0,0c0,0,0,0,0-0.1c-0.2,0-0.1,0.2-0.2,0.3c0,0,0,0,0,0c0,0,0,0,0,0l0,0
c0,0,0,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0.1,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.1,0,0.1c0.1,0.1,0.1,0.3,0.1,0.3c0,0,0,0,0,0
c0,0,0,0,0,0.1c0,0-0.1,0-0.1,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0-0.1,0-0.1,0.1c0,0,0,0,0,0.1c0.4,0,0.9,0,1.4,0c0.6,0,1.2,0,1.7,0
C9.1,20,9.3,19.9,9.3,19.8"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="7.4653" y1="9.0342" x2="7.4653" y2="23.9663">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_8_);" d="M7.5,9C3.3,9,0,12.4,0,16.5C0,20.6,3.3,24,7.5,24c4.1,0,7.5-3.3,7.5-7.5
C14.9,12.4,11.6,9,7.5,9z M13.7,18.6c-0.1,0.3-0.2,0.6-0.4,0.9s-0.3,0.6-0.5,0.9s-0.4,0.5-0.6,0.8s-0.5,0.5-0.8,0.6
c-0.3,0.2-0.6,0.4-0.9,0.5c-0.3,0.2-0.6,0.3-0.9,0.4s-0.7,0.2-1,0.2C8.2,23,7.9,23,7.5,23C1,22.7-0.9,15.2,3.3,11.6
C5,10.1,6.8,10,7.5,10c0.4,0,0.7,0,1.1,0.1c0.3,0.1,0.7,0.1,1,0.2s0.6,0.2,0.9,0.4c0.3,0.2,0.6,0.3,0.9,0.5c0.3,0.2,0.5,0.4,0.8,0.6
c0.2,0.2,0.5,0.5,0.6,0.8c0.2,0.3,0.4,0.6,0.5,0.9s0.3,0.6,0.4,0.9s0.2,0.7,0.2,1c0.1,0.3,0.1,0.7,0.1,1.1c0,0.4,0,0.7-0.1,1.1
C13.9,17.9,13.8,18.2,13.7,18.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -0,0 +1,228 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="18" y1="13" x2="18" y2="20.0005">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="12" y="13" style="fill:url(#SVGID_1_);" width="12" height="7"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="17.5" y1="14" x2="17.5" y2="19">
<stop offset="0" style="stop-color:#F7F7F7"/>
<stop offset="0.1044" style="stop-color:#FCFCFC"/>
<stop offset="0.3293" style="stop-color:#FFFFFF"/>
<stop offset="0.5692" style="stop-color:#E8E8E8"/>
<stop offset="0.8153" style="stop-color:#D7D7D7"/>
<stop offset="1" style="stop-color:#D1D1D1"/>
<a:midPointStop offset="0" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.2222" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.3293" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0.3545" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="1" style="stop-color:#D1D1D1"/>
</linearGradient>
<rect x="12" y="14" style="fill:url(#SVGID_2_);" width="11" height="5"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="17" y1="15" x2="17" y2="18">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="12" y="15" style="fill:url(#SVGID_3_);" width="10" height="3"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="16.5" y1="0" x2="16.5" y2="24.0005">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="13" style="fill:url(#SVGID_4_);" width="7" height="24"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="16.5" y1="1" x2="16.5" y2="23.0005">
<stop offset="0" style="stop-color:#F7F7F7"/>
<stop offset="0.1044" style="stop-color:#FCFCFC"/>
<stop offset="0.3293" style="stop-color:#FFFFFF"/>
<stop offset="0.5692" style="stop-color:#E8E8E8"/>
<stop offset="0.8153" style="stop-color:#D7D7D7"/>
<stop offset="1" style="stop-color:#D1D1D1"/>
<a:midPointStop offset="0" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.2222" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.3293" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0.3545" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="1" style="stop-color:#D1D1D1"/>
</linearGradient>
<rect x="14" y="1" style="fill:url(#SVGID_5_);" width="5" height="22"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="16.5" y1="2" x2="16.5" y2="22.0005">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="15" y="2" style="fill:url(#SVGID_6_);" width="3" height="20"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="18" y1="4" x2="18" y2="11">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="12" y="4" style="fill:url(#SVGID_7_);" width="12" height="7"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="17.5" y1="5" x2="17.5" y2="10">
<stop offset="0" style="stop-color:#F7F7F7"/>
<stop offset="0.1044" style="stop-color:#FCFCFC"/>
<stop offset="0.3293" style="stop-color:#FFFFFF"/>
<stop offset="0.5692" style="stop-color:#E8E8E8"/>
<stop offset="0.8153" style="stop-color:#D7D7D7"/>
<stop offset="1" style="stop-color:#D1D1D1"/>
<a:midPointStop offset="0" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.2222" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.3293" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0.3545" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="1" style="stop-color:#D1D1D1"/>
</linearGradient>
<rect x="12" y="5" style="fill:url(#SVGID_8_);" width="11" height="5"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="17" y1="6" x2="17" y2="9">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="12" y="6" style="fill:url(#SVGID_9_);" width="10" height="3"/>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="6" y1="4" x2="6" y2="11">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect y="4" style="fill:url(#SVGID_10_);" width="12" height="7"/>
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="6.5" y1="5" x2="6.5" y2="10">
<stop offset="0" style="stop-color:#F7F7F7"/>
<stop offset="0.1044" style="stop-color:#FCFCFC"/>
<stop offset="0.3293" style="stop-color:#FFFFFF"/>
<stop offset="0.5692" style="stop-color:#E8E8E8"/>
<stop offset="0.8153" style="stop-color:#D7D7D7"/>
<stop offset="1" style="stop-color:#D1D1D1"/>
<a:midPointStop offset="0" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.2222" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.3293" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0.3545" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="1" style="stop-color:#D1D1D1"/>
</linearGradient>
<rect x="1" y="5" style="fill:url(#SVGID_11_);" width="11" height="5"/>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="7" y1="6" x2="7" y2="9">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="2" y="6" style="fill:url(#SVGID_12_);" width="10" height="3"/>
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="7.5" y1="0" x2="7.5" y2="24.0005">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="4" style="fill:url(#SVGID_13_);" width="7" height="24"/>
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="7.5" y1="1" x2="7.5" y2="23.0005">
<stop offset="0" style="stop-color:#F7F7F7"/>
<stop offset="0.1044" style="stop-color:#FCFCFC"/>
<stop offset="0.3293" style="stop-color:#FFFFFF"/>
<stop offset="0.5692" style="stop-color:#E8E8E8"/>
<stop offset="0.8153" style="stop-color:#D7D7D7"/>
<stop offset="1" style="stop-color:#D1D1D1"/>
<a:midPointStop offset="0" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.2222" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.3293" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0.3545" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="1" style="stop-color:#D1D1D1"/>
</linearGradient>
<rect x="5" y="1" style="fill:url(#SVGID_14_);" width="5" height="22"/>
<linearGradient id="SVGID_15_" gradientUnits="userSpaceOnUse" x1="7.5" y1="2" x2="7.5" y2="22.0005">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="6" y="2" style="fill:url(#SVGID_15_);" width="3" height="20"/>
<linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="6" y1="13" x2="6" y2="20.0005">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect y="13" style="fill:url(#SVGID_16_);" width="12" height="7"/>
<linearGradient id="SVGID_17_" gradientUnits="userSpaceOnUse" x1="6.5" y1="14" x2="6.5" y2="19">
<stop offset="0" style="stop-color:#F7F7F7"/>
<stop offset="0.1044" style="stop-color:#FCFCFC"/>
<stop offset="0.3293" style="stop-color:#FFFFFF"/>
<stop offset="0.5692" style="stop-color:#E8E8E8"/>
<stop offset="0.8153" style="stop-color:#D7D7D7"/>
<stop offset="1" style="stop-color:#D1D1D1"/>
<a:midPointStop offset="0" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.2222" style="stop-color:#F7F7F7"/>
<a:midPointStop offset="0.3293" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="0.3545" style="stop-color:#FFFFFF"/>
<a:midPointStop offset="1" style="stop-color:#D1D1D1"/>
</linearGradient>
<rect x="1" y="14" style="fill:url(#SVGID_17_);" width="11" height="5"/>
<linearGradient id="SVGID_18_" gradientUnits="userSpaceOnUse" x1="7" y1="15" x2="7" y2="18">
<stop offset="0" style="stop-color:#8E8E8E"/>
<stop offset="4.191053e-02" style="stop-color:#8A8A8A"/>
<stop offset="0.4613" style="stop-color:#626262"/>
<stop offset="0.7952" style="stop-color:#4A4A4A"/>
<stop offset="1" style="stop-color:#414141"/>
<a:midPointStop offset="0" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="0.4" style="stop-color:#8E8E8E"/>
<a:midPointStop offset="1" style="stop-color:#414141"/>
</linearGradient>
<rect x="2" y="15" style="fill:url(#SVGID_18_);" width="10" height="3"/>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="overflow:visible;enable-background:new 0 0 24 24;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="7.9995" y1="7.9868" x2="7.9995" y2="24.001">
<stop offset="0" style="stop-color:#F0A829"/>
<stop offset="1" style="stop-color:#C7671A"/>
<a:midPointStop offset="0" style="stop-color:#F0A829"/>
<a:midPointStop offset="0.5" style="stop-color:#F0A829"/>
<a:midPointStop offset="1" style="stop-color:#C7671A"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M3,19.2l-3,1.6V24h16v-0.4c0-0.5-0.5-1.2-1-1.5l-6-3.1c-0.5-0.3-0.6-0.8-0.3-1.2
c0,0,1.6-2,1.6-4.2C10.4,10.5,8.4,8,6,8c-2.4,0-4.4,2.6-4.4,5.7c0,2.1,1.6,4.2,1.6,4.2C3.6,18.3,3.5,18.9,3,19.2z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="7.7212" y1="8.9868" x2="7.7212" y2="23.001">
<stop offset="0" style="stop-color:#FFEBA8"/>
<stop offset="1" style="stop-color:#F8BE27"/>
<a:midPointStop offset="0" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="0.5" style="stop-color:#FFEBA8"/>
<a:midPointStop offset="1" style="stop-color:#F8BE27"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M1,23v-1.7L3.5,20c0.5-0.3,0.8-0.7,1-1.2c0.1-0.5,0-1.1-0.4-1.5c0,0-1.4-1.8-1.4-3.6
C2.6,11.1,4.2,9,6,9s3.4,2.1,3.4,4.7c0,1.7-1.4,3.5-1.4,3.6c-0.3,0.4-0.5,1-0.4,1.5c0.1,0.5,0.5,1,1,1.2l5.9,3H1z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="6.1343" y1="9.9868" x2="6.1343" y2="22.001">
<stop offset="0" style="stop-color:#FFC30F"/>
<stop offset="1" style="stop-color:#F5AE0D"/>
<a:midPointStop offset="0" style="stop-color:#FFC30F"/>
<a:midPointStop offset="0.5" style="stop-color:#FFC30F"/>
<a:midPointStop offset="1" style="stop-color:#F5AE0D"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M2,22L2,22l1.9-1.1c0.8-0.4,1.3-1.1,1.5-1.9c0.2-0.8,0-1.7-0.6-2.3c-0.3-0.4-1.2-1.8-1.2-3
c0-2,1.1-3.7,2.4-3.7s2.4,1.7,2.4,3.7c0,1.1-0.9,2.5-1.2,3c-0.5,0.7-0.7,1.5-0.5,2.3c0.2,0.8,0.7,1.5,1.5,1.9l2.2,1.1H2z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="16" y1="7.9868" x2="16" y2="24.001">
<stop offset="0" style="stop-color:#8D470D"/>
<stop offset="1" style="stop-color:#7C3D09"/>
<a:midPointStop offset="0" style="stop-color:#8D470D"/>
<a:midPointStop offset="0.5" style="stop-color:#8D470D"/>
<a:midPointStop offset="1" style="stop-color:#7C3D09"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M24,24v-3.4l-3-1.5c-0.5-0.3-0.6-0.8-0.3-1.2c0,0,1.6-2,1.6-4.2c0-3.2-1.9-5.7-4.4-5.7
c-2.4,0-4.4,2.6-4.4,5.7c0,2.1,1.6,4.2,1.6,4.2c0.3,0.4,0.2,1-0.3,1.3l-6,3.2c-0.5,0.3-1,0.9-1,1.5V24H24z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="16.4121" y1="8.9868" x2="16.4121" y2="23.001">
<stop offset="0" style="stop-color:#D58738"/>
<stop offset="1" style="stop-color:#AB551F"/>
<a:midPointStop offset="0" style="stop-color:#D58738"/>
<a:midPointStop offset="0.5" style="stop-color:#D58738"/>
<a:midPointStop offset="1" style="stop-color:#AB551F"/>
</linearGradient>
<path style="fill:url(#SVGID_5_);" d="M9.8,23l5.7-3c0.5-0.3,0.8-0.7,1-1.2c0.1-0.5,0-1.1-0.4-1.5c0,0-1.4-1.8-1.4-3.6
c0-2.6,1.5-4.7,3.4-4.7s3.4,2.1,3.4,4.7c0,1.8-1.4,3.6-1.4,3.6c-0.3,0.4-0.5,1-0.4,1.5s0.5,1,1,1.2l2.4,1.2V23H9.8z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="17.9424" y1="9.9868" x2="17.9424" y2="22.001">
<stop offset="0" style="stop-color:#D0813A"/>
<stop offset="1" style="stop-color:#AF551D"/>
<a:midPointStop offset="0" style="stop-color:#D0813A"/>
<a:midPointStop offset="0.5" style="stop-color:#D0813A"/>
<a:midPointStop offset="1" style="stop-color:#AF551D"/>
</linearGradient>
<path style="fill:url(#SVGID_6_);" d="M13.9,22l2.1-1.1c0.8-0.4,1.3-1.1,1.5-1.9c0.2-0.8,0-1.7-0.6-2.3c-0.3-0.4-1.2-1.8-1.2-3
c0-2,1.1-3.7,2.4-3.7s2.4,1.7,2.4,3.7c0,1.2-0.9,2.5-1.2,3c-0.5,0.7-0.7,1.5-0.6,2.3c0.2,0.8,0.7,1.5,1.5,1.9l1.9,1V22H13.9z"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="7.4507" y1="0" x2="7.4507" y2="12.9043">
<stop offset="0" style="stop-color:#76A1F0"/>
<stop offset="1" style="stop-color:#6B90D5"/>
<a:midPointStop offset="0" style="stop-color:#76A1F0"/>
<a:midPointStop offset="0.5" style="stop-color:#76A1F0"/>
<a:midPointStop offset="1" style="stop-color:#6B90D5"/>
</linearGradient>
<path style="fill:url(#SVGID_7_);" d="M12.5,10.4c1.4-1.1,2.4-2.6,2.4-4.3c0.1-3.3-3.2-6-7.3-6.1C3.4-0.1,0.1,2.5,0,5.8
c-0.1,3.3,3.2,6,7.3,6.1c1,0,2-0.1,2.9-0.4c0.8,1,2.8,1.4,2.8,1.4S12.2,11.7,12.5,10.4z"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="7.4507" y1="1" x2="7.4507" y2="11.2168">
<stop offset="0" style="stop-color:#BBE0F7"/>
<stop offset="1" style="stop-color:#82B4FB"/>
<a:midPointStop offset="0" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="0.5" style="stop-color:#BBE0F7"/>
<a:midPointStop offset="1" style="stop-color:#82B4FB"/>
</linearGradient>
<path style="fill:url(#SVGID_8_);" d="M11.4,11.2c-0.2-0.1-0.3-0.2-0.4-0.3l-0.4-0.6l-0.7,0.2c-0.8,0.2-1.6,0.4-2.4,0.4l-0.1,0
C3.8,10.8,1,8.6,1,5.8C1,3.2,3.9,1,7.4,1l0.2,0c1.8,0,3.4,0.6,4.6,1.6c1.1,1,1.8,2.2,1.7,3.5c0,1.3-0.7,2.6-2,3.5l-0.3,0.2l-0.1,0.4
C11.4,10.5,11.4,10.9,11.4,11.2z"/>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="7.4507" y1="2" x2="7.4507" y2="9.9097">
<stop offset="0" style="stop-color:#95BFF8"/>
<stop offset="0.5569" style="stop-color:#84ADEF"/>
<stop offset="1" style="stop-color:#7CA4EB"/>
<a:midPointStop offset="0" style="stop-color:#95BFF8"/>
<a:midPointStop offset="0.4" style="stop-color:#95BFF8"/>
<a:midPointStop offset="1" style="stop-color:#7CA4EB"/>
</linearGradient>
<path style="fill:url(#SVGID_9_);" d="M7.4,9.9C4.4,9.9,2,8,2,5.9C2,3.8,4.5,2,7.4,2l0.1,0c1.5,0,2.9,0.5,4,1.4
c0.9,0.8,1.4,1.7,1.4,2.7c0,1-0.6,2-1.6,2.7l-0.6,0.4l0,0.1L9.6,9.6C9,9.8,8.2,9.9,7.5,9.9L7.4,9.9z"/>
<path style="fill:#FFFFFF;" d="M5.5,4.5h1.9V6c0,0.5-0.1,1-0.3,1.3C6.9,7.6,6.5,7.9,5.9,8.1L5.5,7.3C5.9,7.2,6.1,7,6.2,6.9
c0.1-0.2,0.2-0.3,0.2-0.6H5.5V4.5z M7.7,4.5h1.9V6c0,0.5-0.1,1-0.3,1.3C9.1,7.6,8.7,7.9,8.2,8.1L7.7,7.3C8.1,7.2,8.3,7,8.4,6.9
c0.1-0.2,0.2-0.3,0.2-0.6H7.7V4.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit } from '@angular/core';
import { NavParams, PopoverController } from '@ionic/angular';
import { Component, Input, OnInit } from '@angular/core';
import { PopoverController } from '@ionic/angular';
import { CoreCourses } from '../../services/courses';
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses-helper';
import { CorePrefetchStatusInfo } from '@features/course/services/course-helper';
@ -27,18 +27,14 @@ import { CorePrefetchStatusInfo } from '@features/course/services/course-helper'
})
export class CoreCoursesCourseOptionsMenuComponent implements OnInit {
course!: CoreEnrolledCourseDataWithExtraInfoAndOptions; // The course.
prefetch!: CorePrefetchStatusInfo; // The prefecth info.
@Input() course!: CoreEnrolledCourseDataWithExtraInfoAndOptions; // The course.
@Input() prefetch!: CorePrefetchStatusInfo; // The prefecth info.
downloadCourseEnabled = false;
constructor(
navParams: NavParams,
protected popoverController: PopoverController,
) {
this.course = navParams.get('course') || {};
this.prefetch = navParams.get('prefetch') || {};
}
) { }
/**
* Component being initialized.

View File

@ -16,7 +16,7 @@
<form (ngSubmit)="submitPassword($event)" #enrolPasswordForm>
<ion-item>
<ion-label>
<core-show-password [name]="'password'">
<core-show-password name="password">
<ion-input
class="ion-text-wrap core-ioninput-password"
name="password"

View File

@ -46,7 +46,7 @@ export class CoreCoursesAvailableCoursesPage implements OnInit {
* @return Promise resolved when done.
*/
protected async loadCourses(): Promise<void> {
const frontpageCourseId = CoreSites.instance.getCurrentSite()!.getSiteHomeId();
const frontpageCourseId = CoreSites.instance.getCurrentSiteHomeId();
try {
const courses = await CoreCourses.instance.getCoursesByField();

View File

@ -17,7 +17,7 @@ import { Injectable } from '@angular/core';
import { CoreUtils } from '@services/utils/utils';
import { CoreSites } from '@services/sites';
import { CoreCourses, CoreCourseSearchedData, CoreCourseUserAdminOrNavOptionIndexed, CoreEnrolledCourseData } from './courses';
import { makeSingleton } from '@singletons';
import { makeSingleton, Translate } from '@singletons';
import { CoreWSExternalFile } from '@services/ws';
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
// import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
@ -34,8 +34,30 @@ export class CoreCoursesHelperProvider {
* @param courseId Course ID to get the category.
* @return Promise resolved with the list of courses and the category.
*/
async getCoursesForPopover(): Promise<void> {
// @todo params and logic
async getCoursesForPopover(courseId?: number): Promise<{courses: Partial<CoreEnrolledCourseData>[]; categoryId?: number}> {
const courses: Partial<CoreEnrolledCourseData>[] = await CoreCourses.instance.getUserCourses(false);
// Add "All courses".
courses.unshift({
id: -1,
fullname: Translate.instance.instant('core.fulllistofcourses'),
categoryid: -1,
});
let categoryId: number | undefined;
if (courseId) {
// Search the course to get the category.
const course = courses.find((course) => course.id == courseId);
if (course) {
categoryId = course.categoryid;
}
}
return {
courses: courses,
categoryId: categoryId,
};
}
/**

View File

@ -39,7 +39,7 @@
</ion-item>
<ion-item *ngIf="siteChecked && !isBrowserSSO" class="ion-margin-bottom">
<ion-label>
<core-show-password [name]="'password'">
<core-show-password name="password">
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go"
required="true">

View File

@ -106,7 +106,7 @@
<ion-label position="stacked">
<span core-mark-required="true">{{ 'core.login.password' | translate }}</span>
</ion-label>
<core-show-password [name]="'password'">
<core-show-password name="password">
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
formControlName="password" [clearOnEdit]="false" autocomplete="new-password" required="true">
</ion-input>

View File

@ -40,7 +40,7 @@
</ion-item>
<ion-item class="ion-margin-bottom">
<ion-label>
<core-show-password [name]="'password'">
<core-show-password name="password">
<ion-input class="core-ioninput-password" name="password" type="password"
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
autocomplete="current-password" enterkeyhint="go" required="true">

View File

@ -81,7 +81,7 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
}, CoreSites.instance.getCurrentSiteId());
this.currentSite = CoreSites.instance.getCurrentSite()!;
this.siteHomeId = this.currentSite?.getSiteHomeId() || 1;
this.siteHomeId = CoreSites.instance.getCurrentSiteHomeId();
const module = navParams['module'];
if (module) {

View File

@ -47,7 +47,7 @@ export class CoreSiteHomeProvider {
*/
async getNewsForum(siteHomeId?: number): Promise<AddonModForumData> {
if (!siteHomeId) {
siteHomeId = CoreSites.instance.getCurrentSite()?.getSiteHomeId() || 1;
siteHomeId = CoreSites.instance.getCurrentSiteHomeId();
}
const forums = await AddonModForum.instance.getCourseForums(siteHomeId);

View File

@ -17,7 +17,7 @@ import { IonRefresher } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom';
// import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreTag } from '@features/tag/services/tag';
import { CoreTagAreaDelegate } from '@/core/features/tag/services/tag-area-delegate';
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
import { ActivatedRoute, Router } from '@angular/router';
import { CoreTagFeedElement } from '../../services/tag-helper';

View File

@ -17,9 +17,9 @@ import { Routes } from '@angular/router';
import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
import { CoreMainMenuRoutingModule } from '../mainmenu/mainmenu-routing.module';
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreTagMainMenuHandler, CoreTagMainMenuHandlerService } from './services/handlers/tag.mainmenu';
import { CoreTagIndexLinkHandler } from './services/handlers/index.link';
import { CoreTagSearchLinkHandler } from './services/handlers/search.link';
import { CoreTagMainMenuHandler, CoreTagMainMenuHandlerService } from './services/handlers/mainmenu';
import { CoreTagIndexLinkHandler } from './services/handlers/index-link';
import { CoreTagSearchLinkHandler } from './services/handlers/search-link';
const routes: Routes = [
{

View File

@ -14,7 +14,7 @@
import { Component, Input } from '@angular/core';
import { CoreUserTagFeedElement } from '@features/user/services/handlers/tag-area-handler';
import { CoreUserTagFeedElement } from '@features/user/services/handlers/tag-area';
/**
* Component to render the user tag area.

View File

@ -58,8 +58,8 @@ export class CoreUserAboutPage implements OnInit {
* @return Promise resolved when done.
*/
async ngOnInit(): Promise<void> {
this.userId = this.route.snapshot.queryParams['userId'];
this.courseId = this.route.snapshot.queryParams['courseId'];
this.userId = parseInt(this.route.snapshot.queryParams['userId'], 10) || 0;
this.courseId = parseInt(this.route.snapshot.queryParams['courseId'], 10) || 0;
this.fetchUser().finally(() => {
this.userLoaded = true;

View File

@ -81,8 +81,8 @@ export class CoreUserProfilePage implements OnInit, OnDestroy {
*/
async ngOnInit(): Promise<void> {
this.site = CoreSites.instance.getCurrentSite();
this.userId = this.route.snapshot.queryParams['userId'];
this.courseId = this.route.snapshot.queryParams['courseId'];
this.userId = parseInt(this.route.snapshot.queryParams['userId'], 10);
this.courseId = parseInt(this.route.snapshot.queryParams['courseId'], 10);
if (!this.site) {
return;

View File

@ -25,7 +25,7 @@ import { CoreContentLinksDelegate } from '@features/contentlinks/services/conten
import { CoreUserProfileLinkHandler } from './services/handlers/profile-link';
import { CoreCronDelegate } from '@services/cron';
import { CoreUserSyncCronHandler } from './services/handlers/sync-cron';
import { CoreUserTagAreaHandler } from './services/handlers/tag-area-handler';
import { CoreUserTagAreaHandler } from './services/handlers/tag-area';
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
const routes: Routes = [

View File

@ -0,0 +1,54 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Pipe, PipeTransform } from '@angular/core';
import { CoreLogger } from '@singletons/logger';
import { CoreTimeUtils } from '@services/utils/time';
/**
* Filter to turn a number of seconds to a duration. E.g. 60 -> 1 minute.
*/
@Pipe({
name: 'coreDuration',
})
export class CoreDurationPipe implements PipeTransform {
protected logger: CoreLogger;
constructor() {
this.logger = CoreLogger.getInstance('CoreBytesToSizePipe');
}
/**
* Turn a number of seconds to a duration. E.g. 60 -> 1 minute.
*
* @param seconds The number of seconds.
* @return Formatted duration.
*/
transform(seconds: string | number): string {
if (typeof seconds == 'string') {
// Convert the value to a number.
const numberSeconds = parseInt(seconds, 10);
if (isNaN(numberSeconds)) {
this.logger.error('Invalid value received', seconds);
return seconds;
}
seconds = numberSeconds;
}
return CoreTimeUtils.instance.formatTime(seconds);
}
}

View File

@ -19,6 +19,7 @@ import { CoreNoTagsPipe } from './no-tags';
import { CoreSecondsToHMSPipe } from './seconds-to-hms';
import { CoreTimeAgoPipe } from './time-ago';
import { CoreBytesToSizePipe } from './bytes-to-size';
import { CoreDurationPipe } from './duration';
@NgModule({
declarations: [
@ -28,6 +29,7 @@ import { CoreBytesToSizePipe } from './bytes-to-size';
CoreFormatDatePipe,
CoreBytesToSizePipe,
CoreSecondsToHMSPipe,
CoreDurationPipe,
],
imports: [],
exports: [
@ -37,6 +39,7 @@ import { CoreBytesToSizePipe } from './bytes-to-size';
CoreFormatDatePipe,
CoreBytesToSizePipe,
CoreSecondsToHMSPipe,
CoreDurationPipe,
],
})
export class CorePipesModule {}

Some files were not shown because too many files have changed in this diff Show More