WIP: group pictures

master
Max 5 years ago
parent bb10f34ef0
commit d6f2af77c3

@ -20,6 +20,8 @@ import {ChatlistComponent} from './components/chatlist/chatlist.component';
import {PostlistComponent} from './components/feed/postlist/postlist.component'; import {PostlistComponent} from './components/feed/postlist/postlist.component';
import {HttpClientModule} from '@angular/common/http'; import {HttpClientModule} from '@angular/common/http';
import {ProfileComponent} from './components/profile/profile.component'; import {ProfileComponent} from './components/profile/profile.component';
import {DialogFileUploadComponent} from './components/profile/fileUpload/fileUpload.component';
import {DialogGroupFileUploadComponent} from './components/group/fileUpload/fileUpload.component';
import {ImprintComponent} from './components/imprint/imprint.component'; import {ImprintComponent} from './components/imprint/imprint.component';
import {AboutComponent} from './components/about/about.component'; import {AboutComponent} from './components/about/about.component';
import {ChatcontactsComponent} from './components/chatmanager/chatcontacts/chatcontacts.component'; import {ChatcontactsComponent} from './components/chatmanager/chatcontacts/chatcontacts.component';
@ -59,7 +61,6 @@ import {MatExpansionModule} from '@angular/material/expansion';
import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatDatepickerModule} from '@angular/material/datepicker';
import {MatNativeDateModule, MatProgressBarModule} from '@angular/material/'; import {MatNativeDateModule, MatProgressBarModule} from '@angular/material/';
import {MatSnackBarModule} from '@angular/material/snack-bar'; import {MatSnackBarModule} from '@angular/material/snack-bar';
import {DialogFileUploadComponent} from './components/profile/fileUpload/fileUpload.component';
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
@ -101,6 +102,7 @@ const appRoutes: Routes = [
DialogCreateEventComponent, DialogCreateEventComponent,
UserlistComponent, UserlistComponent,
DialogFileUploadComponent, DialogFileUploadComponent,
DialogGroupFileUploadComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -150,7 +152,8 @@ const appRoutes: Routes = [
entryComponents: [ entryComponents: [
DialogCreateGroupComponent, DialogCreateGroupComponent,
DialogCreateEventComponent, DialogCreateEventComponent,
DialogFileUploadComponent DialogFileUploadComponent,
DialogGroupFileUploadComponent
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

@ -0,0 +1,29 @@
@import '../../../../styles/greenvironment-material-theme'
.dialogFormField
width: 100%
#error
margin-top: 1em
#progress-bar
margin-top: 1em
.confirmationButton
background-color: $primary-color
.uploadDialogContent
overflow: hidden
text-align: center
#inputPreview
max-width: 75%
max-height: 100%
width: auto
clip-path: circle()
mask-mode: luminance
#inputPreviewWrapper
margin: auto
text-align: center
max-height: 512px

@ -0,0 +1,84 @@
import {Component} from '@angular/core';
import {MatDialogRef} from '@angular/material/dialog';
import {SelfService} from '../../../services/selfservice/self.service';
import {environment} from '../../../../environments/environment';
import {BehaviorSubject} from 'rxjs';
@Component({
selector: 'group-file-upload-dialog',
templateUrl: 'fileUploadDialog.component.html',
styleUrls: ['./fileUpload.component.sass'],
})
export class DialogGroupFileUploadComponent {
public errorOccurred = false;
public uploading = false;
private errorMessage: string;
public profilePictureUrl: BehaviorSubject<string | null>;
private file;
public localFileUrl;
constructor(public dialogRef: MatDialogRef<DialogGroupFileUploadComponent>, private selfService: SelfService) {
this.profilePictureUrl = new BehaviorSubject<string | null>(null);
}
/**
* Getter for the error message
*/
getErrorMessage() {
return this.errorMessage;
}
/**
* Fired when the cancel button of the dialog is pressed
*/
onCancelClicked() {
this.dialogRef.close();
}
/**
* Fired when the ok button was pressed
*/
onOkClicked() {
if (this.file) {
this.errorOccurred = false;
this.uploading = true;
this.selfService.changeProfilePicture(this.file).subscribe((response) => {
this.uploading = false;
if (response.success) {
this.profilePictureUrl.next(environment.greenvironmentUrl + response.fileName);
this.dialogRef.close();
} else {
this.errorMessage = response.error;
this.errorOccurred = true;
}
}, (error) => {
this.uploading = false;
this.errorOccurred = true;
console.error(error);
if (error.error) {
this.errorMessage = error.error.error;
} else {
this.errorMessage = 'Failed to upload the profile picture.';
}
});
} else {
this.errorOccurred = true;
this.errorMessage = 'Please select a file to upload.';
}
}
/**
* Fired when the input of the file select changes.
* @param event
*/
onFileInputChange(event) {
this.errorOccurred = false;
this.errorMessage = '';
this.file = event.target.files[0];
const reader = new FileReader();
reader.onload = (e: any) => {
this.localFileUrl = e.target.result;
};
reader.readAsDataURL(this.file);
}
}

@ -0,0 +1,17 @@
<div id="file-upload-dialog">
<h1 mat-dialog-title align="center">Upload a new group picture!</h1>
<div class="uploadDialogContent" mat-dialog-content>
<input style="display: none" id="input-file" type="file" accept="image/*" (change)="onFileInputChange($event)" #name>
<label for="input-file" class="mat-button mat-raised-button mat-primary">Choose File</label>
<div id="inputPreviewWrapper">
<h2 *ngIf="localFileUrl">Preview:</h2>
<img *ngIf="localFileUrl" id="inputPreview" [src]="localFileUrl"/>
</div>
<mat-progress-bar id="progress-bar" *ngIf="uploading" mode="indeterminate"></mat-progress-bar>
</div>
<mat-error id="error" *ngIf="errorOccurred">{{getErrorMessage()}}</mat-error>
<div mat-dialog-actions align="end">
<button mat-button (click)="onCancelClicked()">Cancel</button>
<button class="confirmationButton" mat-raised-button cdkFocusInitial (click)="onOkClicked()">Upload</button>
</div>
</div>

@ -3,7 +3,14 @@
<!--on small screen--> <!--on small screen-->
<mat-toolbar id="toolbar" fxShow="true" fxHide.gt-sm="true"> <mat-toolbar id="toolbar" fxShow="true" fxHide.gt-sm="true">
<mat-toolbar-row> <mat-toolbar-row>
<div class="profile-picture"></div> <div class="hover-box" matTooltip="upload new picture" *ngIf="isAdmin" (click)="openFileUploadDialog()">
<img class="profile-picture" [src]="groupProfile.picture"/>
<mat-icon id="icon">camera_alt</mat-icon>
</div>
<div *ngIf="!isAdmin">
<img class="profile-picture" [src]="groupProfile.picture"/>
</div>
<span id="username">{{groupProfile.name}}</span> <span id="username">{{groupProfile.name}}</span>
<div class="button-box"> <div class="button-box">
<button mat-icon-button <button mat-icon-button
@ -38,7 +45,13 @@
<!--on big screen--> <!--on big screen-->
<mat-toolbar id="toolbar" fxShow="true" fxHide.lt-md="true"> <mat-toolbar id="toolbar" fxShow="true" fxHide.lt-md="true">
<mat-toolbar-row> <mat-toolbar-row>
<div class="profile-picture"></div> <div class="hover-box" matTooltip="upload new picture" *ngIf="isAdmin" (click)="openFileUploadDialog()">
<img class="profile-picture" [src]="groupProfile.picture"/>
<mat-icon id="icon">camera_alt</mat-icon>
</div>
<div *ngIf="!isAdmin">
<img class="profile-picture" [src]="groupProfile.picture"/>
</div>
<span id="username">{{groupProfile.name}}</span> <span id="username">{{groupProfile.name}}</span>
<span id="handle" class="pointer" <span id="handle" class="pointer"
(click)="showUserProfile(groupProfile.creator)">created by {{groupProfile.creator.username}} (click)="showUserProfile(groupProfile.creator)">created by {{groupProfile.creator.username}}

@ -31,7 +31,9 @@
[class.selected]="group === selectedGroup" [class.selected]="group === selectedGroup"
tabindex="0"> tabindex="0">
<mat-card-header> <mat-card-header>
<div mat-card-avatar class="profile-picture" (click)="showGroupProfile(group)"></div> <div mat-card-avatar>
<img class="group-picture" [src]="group.picture" (click)="showGroupProfile(group)"/>
</div>
<mat-card-title class="pointer" (click)="showGroupProfile(group)">{{group.name}}</mat-card-title> <mat-card-title class="pointer" (click)="showGroupProfile(group)">{{group.name}}</mat-card-title>
<div class="icon-box"> <div class="icon-box">
<button mat-icon-button class="request-button" (click)="joinGroup(group)" <button mat-icon-button class="request-button" (click)="joinGroup(group)"

@ -22,16 +22,12 @@
margin-top: 0.5em margin-top: 0.5em
outline: none outline: none
user-select: none user-select: none
::ng-deep .mat-card-header-text ::ng-deep .mat-card-header-text
width: 1000% width: 1000%
margin: 0 margin: auto 0 auto 24px
margin-left: 16px
.mat-card-subtitle .mat-card-subtitle
margin: 0 margin: 0
word-break: break-all word-break: break-all
.mat-card-title .mat-card-title
margin: 0 margin: 0
word-break: break-all word-break: break-all
@ -40,11 +36,16 @@
margin-top: 0.5em margin-top: 0.5em
margin-bottom: 0.5em margin-bottom: 0.5em
.profile-picture $mat-card-header-size: 52px !default
background-image: url(https://material.angular.io/assets/img/examples/shiba1.jpg) .group-picture
height: $mat-card-header-size
width: $mat-card-header-size
border-radius: 50%
flex-shrink: 0
background-size: cover background-size: cover
transition-duration: 0.5s
.profile-picture:hover z-index: 10
object-fit: cover
cursor: pointer cursor: pointer
.pointer:hover .pointer:hover

@ -12,7 +12,9 @@
<mat-card class="group-card" *ngFor="let group of user.groups" <mat-card class="group-card" *ngFor="let group of user.groups"
[class.selected]="group === selectedGroup" (click)="showGroupProfile(group)"> [class.selected]="group === selectedGroup" (click)="showGroupProfile(group)">
<mat-card-header> <mat-card-header>
<div mat-card-avatar class="group-picture"></div> <div mat-card-avatar>
<img class="group-picture" [src]="group.picture"/>
</div>
<mat-card-title>{{group.name}}</mat-card-title> <mat-card-title>{{group.name}}</mat-card-title>
</mat-card-header> </mat-card-header>
</mat-card> </mat-card>

@ -9,14 +9,30 @@
box-sizing: border-box box-sizing: border-box
width: 100% width: 100%
margin-top: 0.5em margin-top: 0.5em
cursor: pointer outline: none
user-select: none
::ng-deep .mat-card-header-text
width: 1000%
margin: auto 0 auto 24px
.mat-card-subtitle .mat-card-subtitle
margin: 0 margin: 0
word-break: break-all
.mat-card-title
margin: 0
word-break: break-all
$mat-card-header-size: 52px !default
.group-picture .group-picture
background-image: url(https://material.angular.io/assets/img/examples/shiba1.jpg) height: $mat-card-header-size
width: $mat-card-header-size
border-radius: 50%
flex-shrink: 0
background-size: cover background-size: cover
transition-duration: 0.5s
z-index: 10
object-fit: cover
.mat-card-avatar
height: $mat-card-header-size
width: $mat-card-header-size
#button-box #button-box
text-align: right text-align: right

@ -1,13 +1,19 @@
import {IEvent} from './interfaces/IEvent';
export class Event { export class Event {
id: string; id: string;
name: string; name: string;
date: string; date: string;
joined: boolean; joined: boolean;
constructor(pId: string, pName: string, pdate: string, pjoined: boolean) { public assignFromResponse(eventDataResponse: IEvent) {
this.id = pId; this.id = eventDataResponse.id;
this.name = pName; this.name = eventDataResponse.name;
this.date = pdate; const temp = new Date(Number(eventDataResponse.dueDate));
this.joined = pjoined; this.date = temp.toLocaleString('en-GB');
this.joined = eventDataResponse.joined;
return this;
} }
} }

@ -1,14 +1,56 @@
import {User} from 'src/app/models/user'; import {User} from 'src/app/models/user';
import {Event} from 'src/app/models/event'; import {Event} from 'src/app/models/event';
import {IGroup} from './interfaces/IGroup';
import {environment} from 'src/environments/environment';
export class Group { export class Group {
id: number; id: number;
name: string; name: string;
handle: string;
creator: User = new User(); creator: User = new User();
picture: string;
members: User[] = []; members: User[] = [];
admins: User[] = []; admins: User[] = [];
events: Event[] = []; events: Event[] = [];
joined: boolean; joined: boolean;
allowedToJoinGroup = false; allowedToJoinGroup = false;
public assignFromResponse(groupDataResponse: IGroup) {
if (!groupDataResponse) {
return null;
}
this.id = groupDataResponse.id;
this.name = groupDataResponse.name;
this.picture = this.buildPictureUrl(groupDataResponse.picture);
let user = new User();
this.creator = user.assignFromResponse(groupDataResponse.creator);
if (groupDataResponse.members) {
for (const member of groupDataResponse.members) {
user = new User();
this.members.push(user.assignFromResponse(member));
}
}
if (groupDataResponse.admins) {
for (const admin of groupDataResponse.admins) {
user = new User();
this.admins.push(user.assignFromResponse(admin));
}
}
if (groupDataResponse.events) {
for (const event of groupDataResponse.events) {
const event_ = new Event();
this.events.push(event_.assignFromResponse(event));
}
}
this.joined = groupDataResponse.joined;
return this;
}
buildPictureUrl(path: string): string {
if (path) {
return environment.greenvironmentUrl + path;
} else {
return 'assets/images/default-grouppic.svg';
}
}
} }

@ -1,10 +1,23 @@
import {environment} from 'src/environments/environment';
export class GroupInfo { export class GroupInfo {
id: number; id: number;
name: string; name: string;
picture: string;
allowedToJoinGroup = false; allowedToJoinGroup = false;
constructor(pId: number, pName: string) { constructor(pId: number, pName: string, picture: string) {
this.id = pId; this.id = pId;
this.name = pName; this.name = pName;
this.picture = this.buildPictureUrl(picture);
}
buildPictureUrl(path: string): string {
if (path) {
return environment.greenvironmentUrl + path;
} else {
return 'assets/images/default-grouppic.svg';
}
} }
} }

@ -0,0 +1,10 @@
export interface IEvent {
id: string;
name: string;
dueDate: string;
joined: boolean;
}

@ -6,6 +6,8 @@ export interface IGroup {
name: string; name: string;
picture: string;
creator: IUser; creator: IUser;
admins: IUser[]; admins: IUser[];

@ -57,8 +57,12 @@ export class User {
)); ));
} }
if (userDataResponse.groups) { if (userDataResponse.groups) {
/*for (const group of userDataResponse.groups) {
const group_ = new Group();
this.groups.push(group_.assignFromResponse(group));
} doesnt work because of circular injection*/
this.groups = userDataResponse.groups this.groups = userDataResponse.groups
.map(group => new GroupInfo(group.id, group.name)); .map(group => new GroupInfo(group.id, group.name, group.picture));
} }
if (userDataResponse.chats) { if (userDataResponse.chats) {
this.chatIDs = userDataResponse.chats.map(chat => chat.id); this.chatIDs = userDataResponse.chats.map(chat => chat.id);
@ -71,6 +75,7 @@ export class User {
this.receivedRequests = userDataResponse.receivedRequests this.receivedRequests = userDataResponse.receivedRequests
.map(request => new FriendRequest(request.id, request.sender.id, request.sender.handle, request.sender.name)); .map(request => new FriendRequest(request.id, request.sender.id, request.sender.handle, request.sender.name));
} }
return this;
} }
buildProfilePictureUrl(path: string): string { buildProfilePictureUrl(path: string): string {

@ -1,83 +1,59 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Http} from '@angular/http'; import {HttpClient} from '@angular/common/http';
import {environment} from 'src/environments/environment'; import {environment} from 'src/environments/environment';
import {User} from 'src/app/models/user'; import {User} from 'src/app/models/user';
import {Event} from 'src/app/models/event'; import {Event} from 'src/app/models/event';
import {BehaviorSubject} from 'rxjs'; import {BehaviorSubject} from 'rxjs';
import {Group} from 'src/app/models/group'; import {Group} from 'src/app/models/group';
import {tap} from 'rxjs/operators';
import {BaseService} from '../base.service';
import {IFileUploadResult} from '../../models/interfaces/IFileUploadResult';
const getGroupGraphqlQuery = `query($groupId: ID!) {
getGroup(groupId:$groupId){
id
name
joined
picture
creator{id name handle}
admins{id name handle}
members{id name handle profilePicture}
events{id name dueDate joined}
}
}`;
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class GroupService { export class GroupService extends BaseService {
public group: BehaviorSubject<Group> = new BehaviorSubject(new Group()); public group: BehaviorSubject<Group> = new BehaviorSubject(new Group());
constructor(private http: Http) { constructor(private http: HttpClient) {
} super();
public getGroupData(groupId: string) {
const headers = new Headers();
headers.set('Content-Type', 'application/json');
this.http.post(environment.graphQLUrl, this.buildGetGroupJson(groupId)).subscribe(result => {
// push onto subject
this.group.next(this.renderGroup(result.json()));
return this.group;
});
} }
public buildGetGroupJson(id: string): any { /**
const body = { * Builds the getGroup request body
query: `query($groupId: ID!) { */
getGroup(groupId:$groupId){ private static buildGetGroupBody(id: string): any {
id return {
name query: getGroupGraphqlQuery, variables: {groupId: id}
joined
creator{id name handle}
admins{id name handle}
members{id name handle profilePicture}
events{id name dueDate joined}
}
}`, variables: {
groupId: id
}
}; };
return body;
} }
public renderGroup(response: any): Group { public getGroupData(groupId: string) {
const group = new Group(); const url = environment.graphQLUrl;
if (response.data.getGroup != null) { const headers = new Headers();
group.id = response.data.getGroup.id; headers.set('Content-Type', 'application/json');
group.name = response.data.getGroup.name;
group.creator.userID = response.data.getGroup.creator.id;
group.creator.handle = response.data.getGroup.creator.handle;
group.creator.username = response.data.getGroup.creator.name;
group.joined = response.data.getGroup.joined;
for (const member of response.data.getGroup.members) { return this.http.post(url, GroupService.buildGetGroupBody, {headers: this.headers})
const user = new User(); .pipe(this.retryRated())
user.userID = member.id; .pipe(tap(response => {
user.username = member.name; const group_ = new Group();
user.handle = member.handle; this.group.next(group_.assignFromResponse(response));
user.profilePicture = user.buildProfilePictureUrl(member.profilePicture); return this.group;
group.members.push(user); }));
}
for (const admin of response.data.getGroup.admins) {
const user = new User();
user.userID = admin.id;
user.username = admin.name;
user.handle = admin.handle;
group.admins.push(user);
}
for (const event of response.data.getGroup.events) {
const temp = new Date(Number(event.dueDate));
const date = temp.toLocaleString('en-GB');
group.events.push(new Event(event.id, event.name, date, event.joined));
}
return group;
}
return null;
} }
public createEvent(name: string, date: string, groupId: string) { public createEvent(name: string, date: string, groupId: string) {
@ -98,14 +74,15 @@ export class GroupService {
} }
}; };
this.http.post(environment.graphQLUrl, body).subscribe(response => { this.http.post(environment.graphQLUrl, body, {headers: this.headers})
const event = response.json().data.createEvent; .pipe(this.retryRated())
const temp = new Date(Number(event.dueDate)); .pipe(tap(response => {
const pdate = temp.toLocaleString('en-GB'); const event = new Event();
this.group.next( event.assignFromResponse(response.json().data.createEvent);
this.renderGroup(this.group.getValue().events.push(new Event(event.id, event.name, pdate, event.joined))) const group = this.group.getValue();
); group.events.push(event);
}); this.group.next(group);
}));
} }
public joinEvent(eventId: string) { public joinEvent(eventId: string) {
@ -120,7 +97,8 @@ export class GroupService {
eventId: eventId eventId: eventId
} }
}; };
return this.http.post(environment.graphQLUrl, body); return this.http.post(environment.graphQLUrl, body, {headers: this.headers})
.pipe(this.retryRated());
} }
public leaveEvent(eventId: string) { public leaveEvent(eventId: string) {
@ -135,7 +113,8 @@ export class GroupService {
eventId: eventId eventId: eventId
} }
}; };
return this.http.post(environment.graphQLUrl, body); return this.http.post(environment.graphQLUrl, body, {headers: this.headers})
.pipe(this.retryRated());
} }
} }

@ -33,7 +33,8 @@ const graphqlQuery = `mutation($email: String!, $pwHash: String!) {
}, },
groups { groups {
id, id,
name name,
picture
}, },
chats{ chats{
id id

@ -31,6 +31,7 @@ const graphqlQuery = `query($query: String!, $first: Int, $offset: Int) {
groups{ groups{
id id
name name
picture
creator{id name handle} creator{id name handle}
members{id name handle} members{id name handle}
} }
@ -90,7 +91,7 @@ export class SearchService extends BaseService {
public getGroupsForResponse(response: ISearchRequestResult): Array<GroupInfo> { public getGroupsForResponse(response: ISearchRequestResult): Array<GroupInfo> {
const groups = new Array<GroupInfo>(); const groups = new Array<GroupInfo>();
for (const group of response.data.search.groups) { for (const group of response.data.search.groups) {
groups.push(new GroupInfo(group.id, group.name)); groups.push(new GroupInfo(group.id, group.name, group.picture));
} }
return groups; return groups;
} }

@ -26,7 +26,8 @@ const getSelfGraphqlQuery = `{
}, },
groups { groups {
id, id,
name name,
picture
}, },
chats{ chats{
id id

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="256px"
height="256px"
viewBox="0 0 256 256"
version="1.1"
id="SVGRoot"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="default-grouppic.svg">
<defs
id="defs3721" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8284271"
inkscape:cx="102.34915"
inkscape:cy="126.03557"
inkscape:document-units="px"
inkscape:current-layer="g4729"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1131"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:grid-bbox="true" />
<metadata
id="metadata3724">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
style="display:inline">
<g
id="g4671"
transform="matrix(0.95295459,0,0,0.95295459,6.0218124,5.0688566)" />
<g
id="g4729">
<g
id="g4555"
transform="matrix(0.601511,0,0,0.601511,-14.481854,52.459176)"
style="display:inline">
<a
id="a845">
<circle
style="fill:#8bc34a;fill-opacity:1;stroke:none;stroke-width:6.62765312;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4571"
cx="127.37563"
cy="65.066956"
r="55.672287" />
</a>
<path
style="fill:#8bc34a;fill-opacity:1;stroke:none;stroke-width:5.33326387;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 186.37446,186.04588 c 16.02202,16.28834 -9.2932,49.07975 -58.99883,49.07975 -49.705624,0 -89.999994,-19.61141 -89.999997,-43.8033 -8e-6,-24.1919 40.294365,-43.80331 89.999997,-43.80331 49.70563,0 40.16315,19.37811 58.99883,38.52686 z"
id="path4620"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssss" />
</g>
<g
style="display:inline"
id="g4555-3"
transform="matrix(0.601511,0,0,0.601511,51.382155,98.065036)">
<a
id="a845-5">
<circle
style="fill:#8bc34a;fill-opacity:1;stroke:none;stroke-width:6.62765312;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4571-6"
cx="127.37563"
cy="65.066956"
r="55.672287" />
</a>
<ellipse
ry="43.803303"
rx="90"
cy="191.32233"
cx="127.37563"
id="path4620-2"
style="fill:#8bc34a;fill-opacity:1;stroke:none;stroke-width:5.33326387;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
style="display:inline"
id="g4555-9"
transform="matrix(0.601511,0,0,0.601511,119.22436,50.417304)" />
<g
id="g4555-7"
transform="matrix(-0.601511,0,0,0.601511,270.48185,52.459176)"
style="display:inline">
<a
id="a845-9">
<circle
style="fill:#8bc34a;fill-opacity:1;stroke:none;stroke-width:6.62765312;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4571-2"
cx="127.37563"
cy="65.066956"
r="55.672287" />
</a>
<path
style="fill:#8bc34a;fill-opacity:1;stroke:none;stroke-width:5.33326387;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 186.37446,186.04588 c 16.02202,16.28834 -9.2932,49.07975 -58.99883,49.07975 -49.705624,0 -89.999994,-19.61141 -89.999997,-43.8033 -8e-6,-24.1919 40.294365,-43.80331 89.999997,-43.80331 49.70563,0 40.16315,19.37811 58.99883,38.52686 z"
id="path4620-0"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssss" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

Loading…
Cancel
Save