diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-12-07 16:32:06 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-12-07 16:32:06 +0100 |
commit | 27e1a06c331278e5d37bc5172ee7e4fc968e4b5e (patch) | |
tree | b00296c59e573a80dc8f826dce6fe857eea123c5 /client | |
parent | 4cc66133abf1e37873316999b660c93ab92eb4a0 (diff) | |
download | PeerTube-27e1a06c331278e5d37bc5172ee7e4fc968e4b5e.tar.gz PeerTube-27e1a06c331278e5d37bc5172ee7e4fc968e4b5e.tar.zst PeerTube-27e1a06c331278e5d37bc5172ee7e4fc968e4b5e.zip |
First step upload with new design
Diffstat (limited to 'client')
6 files changed, 145 insertions, 235 deletions
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html index a6b753166..8c071ce12 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html | |||
@@ -67,6 +67,7 @@ | |||
67 | <div class="form-group"> | 67 | <div class="form-group"> |
68 | <label for="privacy">Privacy</label> | 68 | <label for="privacy">Privacy</label> |
69 | <select id="privacy" formControlName="privacy"> | 69 | <select id="privacy" formControlName="privacy"> |
70 | |||
70 | <option></option> | 71 | <option></option> |
71 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | 72 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> |
72 | </select> | 73 | </select> |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss index 0af48fabe..2d0bfc36e 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss | |||
@@ -115,42 +115,7 @@ | |||
115 | } | 115 | } |
116 | } | 116 | } |
117 | 117 | ||
118 | |||
119 | .btn-file { | ||
120 | position: relative; | ||
121 | overflow: hidden; | ||
122 | display: block; | ||
123 | } | ||
124 | |||
125 | .btn-file input[type=file] { | ||
126 | position: absolute; | ||
127 | top: 0; | ||
128 | right: 0; | ||
129 | min-width: 100%; | ||
130 | min-height: 100%; | ||
131 | font-size: 100px; | ||
132 | text-align: right; | ||
133 | filter: alpha(opacity=0); | ||
134 | opacity: 0; | ||
135 | outline: none; | ||
136 | background: white; | ||
137 | cursor: inherit; | ||
138 | display: block; | ||
139 | } | ||
140 | |||
141 | div.file-to-upload { | ||
142 | height: 40px; | ||
143 | |||
144 | .glyphicon-remove { | ||
145 | cursor: pointer; | ||
146 | } | ||
147 | } | ||
148 | |||
149 | .little-information { | 118 | .little-information { |
150 | font-size: 0.8em; | 119 | font-size: 0.8em; |
151 | font-style: italic; | 120 | font-style: italic; |
152 | } | 121 | } |
153 | |||
154 | .label-tags { | ||
155 | margin-bottom: 0; | ||
156 | } | ||
diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html index b4e0f9f7c..f8355f3db 100644 --- a/client/src/app/videos/+video-edit/video-add.component.html +++ b/client/src/app/videos/+video-edit/video-add.component.html | |||
@@ -1,141 +1,45 @@ | |||
1 | <div class="row"> | 1 | <div class="margin-content"> |
2 | <div class="content-padding"> | 2 | <div class="title-page title-page-single"> |
3 | Upload your video | ||
4 | </div> | ||
3 | 5 | ||
4 | <h3>Upload a video</h3> | 6 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> |
5 | 7 | ||
6 | <div *ngIf="error !== undefined" class="alert alert-danger">{{ error }}</div> | 8 | <div class="upload-video-container"> |
9 | <div class="upload-video"> | ||
10 | <div class="icon icon-upload"></div> | ||
7 | 11 | ||
8 | <form novalidate [formGroup]="form"> | 12 | <div class="button-file"> |
9 | <div class="form-group"> | 13 | <span>Select the file to upload</span> |
10 | <label for="name">Name</label> | 14 | <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange($event)" /> |
11 | <input | ||
12 | type="text" class="form-control" id="name" | ||
13 | formControlName="name" | ||
14 | > | ||
15 | <div *ngIf="formErrors.name" class="alert alert-danger"> | ||
16 | {{ formErrors.name }} | ||
17 | </div> | ||
18 | </div> | 15 | </div> |
19 | 16 | ||
20 | <div class="form-group"> | 17 | <div class="form-group"> |
21 | <label for="privacy">Privacy</label> | 18 | <select [(ngModel)]="firstStepPrivacy"> |
22 | <select class="form-control" id="privacy" formControlName="privacy"> | ||
23 | <option></option> | ||
24 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | 19 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> |
25 | </select> | 20 | </select> |
26 | |||
27 | <div *ngIf="formErrors.privacy" class="alert alert-danger"> | ||
28 | {{ formErrors.privacy }} | ||
29 | </div> | ||
30 | </div> | ||
31 | |||
32 | <div class="form-group"> | ||
33 | <input | ||
34 | type="checkbox" id="nsfw" | ||
35 | formControlName="nsfw" | ||
36 | > | ||
37 | <label for="nsfw">This video contains mature or explicit content</label> | ||
38 | </div> | 21 | </div> |
39 | 22 | ||
40 | <div class="form-group"> | 23 | <div class="form-group"> |
41 | <label for="category">Channel</label> | 24 | <select [(ngModel)]="firstStepChannel"> |
42 | <select class="form-control" id="channelId" formControlName="channelId"> | ||
43 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> | 25 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> |
44 | </select> | 26 | </select> |
45 | |||
46 | <div *ngIf="formErrors.channelId" class="alert alert-danger"> | ||
47 | {{ formErrors.channelId }} | ||
48 | </div> | ||
49 | </div> | 27 | </div> |
28 | </div> | ||
50 | 29 | ||
51 | <div class="form-group"> | ||
52 | <label for="category">Category</label> | ||
53 | <select class="form-control" id="category" formControlName="category"> | ||
54 | <option></option> | ||
55 | <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option> | ||
56 | </select> | ||
57 | 30 | ||
58 | <div *ngIf="formErrors.category" class="alert alert-danger"> | 31 | <form *ngIf="isUploadingVideo" novalidate [formGroup]="form"> |
59 | {{ formErrors.category }} | 32 | <my-video-edit |
60 | </div> | 33 | [form]="form" [formErrors]="formErrors" |
61 | </div> | 34 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" |
62 | 35 | ></my-video-edit> | |
63 | <div class="form-group"> | ||
64 | <label for="licence">Licence</label> | ||
65 | <select class="form-control" id="licence" formControlName="licence"> | ||
66 | <option></option> | ||
67 | <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option> | ||
68 | </select> | ||
69 | |||
70 | <div *ngIf="formErrors.licence" class="alert alert-danger"> | ||
71 | {{ formErrors.licence }} | ||
72 | </div> | ||
73 | </div> | ||
74 | |||
75 | <div class="form-group"> | ||
76 | <label for="language">Language</label> | ||
77 | <select class="form-control" id="language" formControlName="language"> | ||
78 | <option></option> | ||
79 | <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option> | ||
80 | </select> | ||
81 | 36 | ||
82 | <div *ngIf="formErrors.language" class="alert alert-danger"> | 37 | <div class="submit-container"> |
83 | {{ formErrors.language }} | 38 | <div class="submit-button" [ngClass]="{ disabled: !form.valid }"> |
39 | <span class="icon icon-validate"></span> | ||
40 | <input type="button" value="Publish" (click)="upload()" /> | ||
84 | </div> | 41 | </div> |
85 | </div> | 42 | </div> |
86 | |||
87 | <div class="form-group"> | ||
88 | <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span> | ||
89 | <tag-input | ||
90 | [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" | ||
91 | formControlName="tags" maxItems="5" modelAsStrings="true" | ||
92 | ></tag-input> | ||
93 | </div> | ||
94 | |||
95 | <div class="form-group"> | ||
96 | <label for="videofile">File</label> | ||
97 | <div class="btn btn-default btn-file"> | ||
98 | <span>Select the video...</span> | ||
99 | <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange($event)" /> | ||
100 | <input type="hidden" name="videofileHidden" formControlName="videofile"/> | ||
101 | </div> | ||
102 | </div> | ||
103 | |||
104 | <div class="file-to-upload"> | ||
105 | <div class="file" *ngIf="filename"> | ||
106 | <span class="filename">{{ filename }}</span> | ||
107 | <span class="glyphicon glyphicon-remove" (click)="removeFile()"></span> | ||
108 | </div> | ||
109 | </div> | ||
110 | |||
111 | <div *ngIf="formErrors.videofile" class="alert alert-danger"> | ||
112 | {{ formErrors.videofile }} | ||
113 | </div> | ||
114 | |||
115 | <div class="form-group"> | ||
116 | <label for="description">Description</label> | ||
117 | <my-video-description formControlName="description"></my-video-description> | ||
118 | |||
119 | <div *ngIf="formErrors.description" class="alert alert-danger"> | ||
120 | {{ formErrors.description }} | ||
121 | </div> | ||
122 | </div> | ||
123 | |||
124 | <div class="progress"> | ||
125 | <progressbar [value]="progressPercent" max="100"> | ||
126 | <ng-template [ngIf]="progressPercent === 100"> | ||
127 | <span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span> | ||
128 | Server is processing the video | ||
129 | </ng-template> | ||
130 | </progressbar> | ||
131 | </div> | ||
132 | |||
133 | <div class="form-group"> | ||
134 | <input | ||
135 | type="button" value="Upload" class="btn btn-default form-control" | ||
136 | (click)="upload()" | ||
137 | > | ||
138 | </div> | ||
139 | </form> | 43 | </form> |
140 | </div> | 44 | </div> |
141 | </div> | 45 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add.component.scss b/client/src/app/videos/+video-edit/video-add.component.scss new file mode 100644 index 000000000..25dfd40d2 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-add.component.scss | |||
@@ -0,0 +1,60 @@ | |||
1 | .upload-video-container { | ||
2 | border-radius: 3px; | ||
3 | background-color: #F7F7F7; | ||
4 | border: 3px solid #EAEAEA; | ||
5 | width: 100%; | ||
6 | height: 440px; | ||
7 | text-align: center; | ||
8 | margin-top: 40px; | ||
9 | display: flex; | ||
10 | justify-content: center; | ||
11 | align-items: center; | ||
12 | |||
13 | .upload-video { | ||
14 | display: flex; | ||
15 | flex-direction: column; | ||
16 | align-items: center; | ||
17 | |||
18 | .icon.icon-upload { | ||
19 | @include icon(90px); | ||
20 | margin-bottom: 25px; | ||
21 | |||
22 | background-image: url('../../../assets/images/video/upload.svg'); | ||
23 | } | ||
24 | |||
25 | .button-file { | ||
26 | position: relative; | ||
27 | overflow: hidden; | ||
28 | display: inline-block; | ||
29 | margin-bottom: 70px; | ||
30 | |||
31 | @include peertube-button; | ||
32 | @include orange-button; | ||
33 | |||
34 | input[type=file] { | ||
35 | position: absolute; | ||
36 | top: 0; | ||
37 | right: 0; | ||
38 | min-width: 100%; | ||
39 | min-height: 100%; | ||
40 | font-size: 100px; | ||
41 | text-align: right; | ||
42 | filter: alpha(opacity=0); | ||
43 | opacity: 0; | ||
44 | outline: none; | ||
45 | background: white; | ||
46 | cursor: inherit; | ||
47 | display: block; | ||
48 | } | ||
49 | } | ||
50 | |||
51 | select { | ||
52 | @include peertube-select(auto); | ||
53 | |||
54 | display: inline-block; | ||
55 | font-size: 15px | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | |||
60 | |||
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts index 989addbd7..071f9a28b 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts | |||
@@ -6,61 +6,33 @@ import { NotificationsService } from 'angular2-notifications' | |||
6 | import { VideoService } from 'app/shared/video/video.service' | 6 | import { VideoService } from 'app/shared/video/video.service' |
7 | import { VideoCreate } from '../../../../../shared' | 7 | import { VideoCreate } from '../../../../../shared' |
8 | import { AuthService, ServerService } from '../../core' | 8 | import { AuthService, ServerService } from '../../core' |
9 | import { | 9 | import { FormReactive } from '../../shared' |
10 | FormReactive, | 10 | import { ValidatorMessage } from '../../shared/forms/form-validators' |
11 | VIDEO_CATEGORY, | 11 | import { VideoEdit } from '../../shared/video/video-edit.model' |
12 | VIDEO_CHANNEL, | ||
13 | VIDEO_DESCRIPTION, | ||
14 | VIDEO_FILE, | ||
15 | VIDEO_LANGUAGE, | ||
16 | VIDEO_LICENCE, | ||
17 | VIDEO_NAME, | ||
18 | VIDEO_PRIVACY, | ||
19 | VIDEO_TAGS | ||
20 | } from '../../shared' | ||
21 | 12 | ||
22 | @Component({ | 13 | @Component({ |
23 | selector: 'my-videos-add', | 14 | selector: 'my-videos-add', |
24 | styleUrls: [ './shared/video-edit.component.scss' ], | 15 | templateUrl: './video-add.component.html', |
25 | templateUrl: './video-add.component.html' | 16 | styleUrls: [ |
17 | './shared/video-edit.component.scss', | ||
18 | './video-add.component.scss' | ||
19 | ] | ||
26 | }) | 20 | }) |
27 | 21 | ||
28 | export class VideoAddComponent extends FormReactive implements OnInit { | 22 | export class VideoAddComponent extends FormReactive implements OnInit { |
29 | @ViewChild('videofileInput') videofileInput | 23 | @ViewChild('videofileInput') videofileInput |
30 | 24 | ||
25 | isUploadingVideo = false | ||
31 | progressPercent = 0 | 26 | progressPercent = 0 |
32 | tags: string[] = [] | ||
33 | videoCategories = [] | ||
34 | videoLicences = [] | ||
35 | videoLanguages = [] | ||
36 | videoPrivacies = [] | ||
37 | userVideoChannels = [] | ||
38 | |||
39 | tagValidators = VIDEO_TAGS.VALIDATORS | ||
40 | tagValidatorsMessages = VIDEO_TAGS.MESSAGES | ||
41 | 27 | ||
42 | error: string | 28 | error: string = null |
43 | form: FormGroup | 29 | form: FormGroup |
44 | formErrors = { | 30 | formErrors: { [ id: string ]: string } = {} |
45 | name: '', | 31 | validationMessages: ValidatorMessage = {} |
46 | privacy: '', | 32 | userVideoChannels = [] |
47 | category: '', | 33 | videoPrivacies = [] |
48 | licence: '', | 34 | firstStepPrivacy = 0 |
49 | language: '', | 35 | firstStepChannel = 0 |
50 | channelId: '', | ||
51 | description: '', | ||
52 | videofile: '' | ||
53 | } | ||
54 | validationMessages = { | ||
55 | name: VIDEO_NAME.MESSAGES, | ||
56 | privacy: VIDEO_PRIVACY.MESSAGES, | ||
57 | category: VIDEO_CATEGORY.MESSAGES, | ||
58 | licence: VIDEO_LICENCE.MESSAGES, | ||
59 | language: VIDEO_LANGUAGE.MESSAGES, | ||
60 | channelId: VIDEO_CHANNEL.MESSAGES, | ||
61 | description: VIDEO_DESCRIPTION.MESSAGES, | ||
62 | videofile: VIDEO_FILE.MESSAGES | ||
63 | } | ||
64 | 36 | ||
65 | constructor ( | 37 | constructor ( |
66 | private formBuilder: FormBuilder, | 38 | private formBuilder: FormBuilder, |
@@ -73,35 +45,17 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
73 | super() | 45 | super() |
74 | } | 46 | } |
75 | 47 | ||
76 | get filename () { | ||
77 | return this.form.value['videofile'] | ||
78 | } | ||
79 | |||
80 | buildForm () { | 48 | buildForm () { |
81 | this.form = this.formBuilder.group({ | 49 | this.form = this.formBuilder.group({}) |
82 | name: [ '', VIDEO_NAME.VALIDATORS ], | ||
83 | nsfw: [ false ], | ||
84 | privacy: [ '', VIDEO_PRIVACY.VALIDATORS ], | ||
85 | category: [ '', VIDEO_CATEGORY.VALIDATORS ], | ||
86 | licence: [ '', VIDEO_LICENCE.VALIDATORS ], | ||
87 | language: [ '', VIDEO_LANGUAGE.VALIDATORS ], | ||
88 | channelId: [ '', VIDEO_CHANNEL.VALIDATORS ], | ||
89 | description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], | ||
90 | videofile: [ '', VIDEO_FILE.VALIDATORS ], | ||
91 | tags: [ '' ] | ||
92 | }) | ||
93 | |||
94 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) | 50 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) |
95 | } | 51 | } |
96 | 52 | ||
97 | ngOnInit () { | 53 | ngOnInit () { |
98 | this.videoCategories = this.serverService.getVideoCategories() | ||
99 | this.videoLicences = this.serverService.getVideoLicences() | ||
100 | this.videoLanguages = this.serverService.getVideoLanguages() | ||
101 | this.videoPrivacies = this.serverService.getVideoPrivacies() | ||
102 | |||
103 | this.buildForm() | 54 | this.buildForm() |
104 | 55 | ||
56 | this.videoPrivacies = this.serverService.getVideoPrivacies() | ||
57 | this.firstStepPrivacy = this.videoPrivacies[0].id | ||
58 | |||
105 | this.authService.userInformationLoaded | 59 | this.authService.userInformationLoaded |
106 | .subscribe( | 60 | .subscribe( |
107 | () => { | 61 | () => { |
@@ -112,21 +66,13 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
112 | if (Array.isArray(videoChannels) === false) return | 66 | if (Array.isArray(videoChannels) === false) return |
113 | 67 | ||
114 | this.userVideoChannels = videoChannels.map(v => ({ id: v.id, label: v.name })) | 68 | this.userVideoChannels = videoChannels.map(v => ({ id: v.id, label: v.name })) |
115 | 69 | this.firstStepChannel = this.userVideoChannels[0].id | |
116 | this.form.patchValue({ channelId: this.userVideoChannels[0].id }) | ||
117 | } | 70 | } |
118 | ) | 71 | ) |
119 | } | 72 | } |
120 | 73 | ||
121 | // The goal is to keep reactive form validation (required field) | ||
122 | // https://stackoverflow.com/a/44238894 | ||
123 | fileChange ($event) { | 74 | fileChange ($event) { |
124 | this.form.controls['videofile'].setValue($event.target.files[0].name) | 75 | console.log('uploading file ?') |
125 | } | ||
126 | |||
127 | removeFile () { | ||
128 | this.videofileInput.nativeElement.value = '' | ||
129 | this.form.controls['videofile'].setValue('') | ||
130 | } | 76 | } |
131 | 77 | ||
132 | checkForm () { | 78 | checkForm () { |
@@ -135,11 +81,7 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
135 | return this.form.valid | 81 | return this.form.valid |
136 | } | 82 | } |
137 | 83 | ||
138 | upload () { | 84 | uploadFirstStep () { |
139 | if (this.checkForm() === false) { | ||
140 | return | ||
141 | } | ||
142 | |||
143 | const formValue: VideoCreate = this.form.value | 85 | const formValue: VideoCreate = this.form.value |
144 | 86 | ||
145 | const name = formValue.name | 87 | const name = formValue.name |
@@ -193,4 +135,26 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
193 | } | 135 | } |
194 | ) | 136 | ) |
195 | } | 137 | } |
138 | |||
139 | updateSecondStep () { | ||
140 | if (this.checkForm() === false) { | ||
141 | return | ||
142 | } | ||
143 | |||
144 | const video = new VideoEdit(this.form.value) | ||
145 | |||
146 | this.videoService.updateVideo(video) | ||
147 | .subscribe( | ||
148 | () => { | ||
149 | this.notificationsService.success('Success', 'Video published.') | ||
150 | this.router.navigate([ '/videos/watch', video.uuid ]) | ||
151 | }, | ||
152 | |||
153 | err => { | ||
154 | this.error = 'Cannot update the video.' | ||
155 | console.error(err) | ||
156 | } | ||
157 | ) | ||
158 | |||
159 | } | ||
196 | } | 160 | } |
diff --git a/client/src/assets/images/video/upload.svg b/client/src/assets/images/video/upload.svg new file mode 100644 index 000000000..c5b7cb443 --- /dev/null +++ b/client/src/assets/images/video/upload.svg | |||
@@ -0,0 +1,16 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2 | <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||
3 | <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch --> | ||
4 | <title>cloud-upload</title> | ||
5 | <desc>Created with Sketch.</desc> | ||
6 | <defs></defs> | ||
7 | <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round"> | ||
8 | <g id="Artboard-4" transform="translate(-312.000000, -775.000000)" stroke="#C6C6C6" stroke-width="2"> | ||
9 | <g id="307" transform="translate(312.000000, 775.000000)"> | ||
10 | <path d="M8,18 L5,18 L5,18 C2.790861,18 1,16.209139 1,14 C1,11.790861 2.790861,10 5,10 C5.35840468,10 5.70579988,10.0471371 6.03632437,10.1355501 C6.01233106,9.92702603 6,9.71495305 6,9.5 C6,6.46243388 8.46243388,4 11.5,4 C14.0673313,4 16.2238156,5.7590449 16.8299648,8.1376465 C17.2052921,8.04765874 17.5970804,8 18,8 C20.7614237,8 23,10.2385763 23,13 C23,15.7614237 20.7614237,18 18,18 L16,18" id="Combined-Shape" stroke-linejoin="round"></path> | ||
11 | <path d="M12,13 L12,21" id="Path-58"></path> | ||
12 | <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 12.500000) scale(1, -1) translate(-12.000000, -12.500000) " points="15 11 12 14 9 11"></polyline> | ||
13 | </g> | ||
14 | </g> | ||
15 | </g> | ||
16 | </svg> | ||