|
@ -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,
|
||||
|
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 { }
|
|
@ -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 {}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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 can’t or won’t 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,5 @@
|
|||
:host {
|
||||
.addon-calendar-event {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
|
@ -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 can’t or won’t 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
|
@ -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 {}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
:host {
|
||||
.addon-calendar-period {
|
||||
flex-grow: 3;
|
||||
h3 {
|
||||
margin-top: 10px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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 {}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
:host {
|
||||
.addon-calendar-eventtype-container.item-select-disabled {
|
||||
ion-label, ion-select {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ion-select::part(icon) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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 {}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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 {}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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>-->
|
|
@ -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 {}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
:host {
|
||||
ion-note {
|
||||
max-width: 30%;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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 {}
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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) {}
|
||||
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 = [
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {}
|
||||
|
|