Skip to content

Commit dca92f7

Browse files
authored
Merge pull request #189 from VanishMax/develop
Add image resizing and fix some phrases
2 parents 9a8a81f + 599b0aa commit dca92f7

File tree

12 files changed

+148
-55
lines changed

12 files changed

+148
-55
lines changed

get_css_bundles.py

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
'create-project/activity',
8585
'create-project/actions',
8686
'page-components/create-activity',
87+
'page-components/modal-dialog',
8788
],
8889
'projects_past': [
8990
'page-components/tagline',

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"stylelint-config-standard": "^20.0.0",
6969
"stylelint-scss": "^3.16.1",
7070
"svelte": "^3.18.0",
71+
"svelte-easy-crop": "^1.0.3",
7172
"swiper": "^5.3.7"
7273
},
7374
"repository": {
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<script>
2+
import { createEventDispatcher } from 'svelte';
3+
import Button from 'ui/button.svelte';
4+
import Dialog from 'ui/dialog.svelte';
5+
import Modal from 'ui/modal.svelte';
6+
import Cropper from 'svelte-easy-crop';
7+
8+
export let image;
9+
let crop = { x: 0, y: 0 };
10+
let pixels = null;
11+
export let aspectRatio = 1;
12+
export let isOpen = false;
13+
export let error = null;
14+
15+
function saveBounds(event) {
16+
pixels = event.detail.pixels;
17+
}
18+
19+
const dispatch = createEventDispatcher();
20+
</script>
21+
22+
<style>
23+
.image-resizer {
24+
position: relative;
25+
width: 15em;
26+
border-radius: .5em;
27+
overflow: hidden;
28+
height: 9em;
29+
}
30+
31+
@media only screen and (min-width: 360px) {
32+
.image-resizer {
33+
width: 18em;
34+
height: 10em;
35+
}
36+
}
37+
38+
@media only screen and (min-width: 640px) {
39+
.image-resizer {
40+
width: 32em;
41+
height: 18em;
42+
}
43+
}
44+
</style>
45+
46+
<Modal bind:isOpen>
47+
{#if image != null}
48+
<Dialog
49+
title="Resize the image"
50+
closeCallback={() => isOpen = false}
51+
>
52+
<div class="image-resizer" slot="content">
53+
<Cropper {image} {crop} aspect={aspectRatio} on:cropcomplete={saveBounds} />
54+
</div>
55+
<div class="actions" slot="content">
56+
{#if error != null}
57+
{error}
58+
{/if}
59+
<Button isDanger on:click={() => isOpen = false}>cancel</Button>
60+
<Button
61+
isFilled
62+
on:click={() => dispatch('image-cropped', pixels)}
63+
>
64+
upload
65+
</Button>
66+
</div>
67+
</Dialog>
68+
{/if}
69+
</Modal>

src/components/projects/new/edit-activity.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@
216216
classname="work-hours"
217217
id="work-hours{index}"
218218
title="Work hours"
219-
subtitle="Reward rate: {HOURLY_RATE} ipts/hour"
219+
subtitle="Estimated time of work, rewarded {HOURLY_RATE} ipts/hour"
220220
error={
221221
(errors.workingHoursNotSpecified && "Working hours must be specified.")
222222
|| (errors.workingHoursInvalid && "Must be positive.")

src/components/projects/new/project-image-picker.svelte

+7-50
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,28 @@
11
<script>
2+
import { createEventDispatcher } from 'svelte';
23
import Button from 'ui/button.svelte';
34
import FileInput from 'ui/file-input.svelte';
4-
import * as api from '@/utils/api.js';
55
import { API_HOST_BROWSER } from '@/constants/env.js';
66
import maxSizeMB from '@/constants/backend/file-upload-limit.js';
77
88
export let value = null;
9-
let promise = null;
10-
let blobURL = null;
119
let error = null;
1210
13-
async function handleImageUpload(event) {
14-
let file = event.target.files[0];
11+
function validateUpload(event) {
12+
const file = event.target.files[0];
1513
if (file.size > maxSizeMB * 1024 * 1024) {
1614
error = `Selected file is too large (${maxSizeMB} MB max).`;
1715
return;
1816
}
19-
20-
blobURL = URL.createObjectURL(file, { type: file.type });
21-
const formData = new FormData();
22-
formData.append('file', file);
23-
24-
promise = api.post('/file', {
25-
data: formData,
26-
});
27-
28-
try {
29-
const resp = await promise;
30-
if (resp.ok) {
31-
value = (await resp.json()).id;
32-
error = null;
33-
} else {
34-
console.error(await resp.text());
35-
error = 'Upload failed. Try again.';
36-
}
37-
} catch (e) {
38-
console.error(e);
39-
error = 'Upload failed. Try again.';
40-
}
17+
dispatch('resize-image', file);
4118
}
4219
4320
function clearValue() {
4421
value = null;
45-
promise = null;
46-
blobURL = null;
4722
error = null;
4823
}
24+
25+
const dispatch = createEventDispatcher();
4926
</script>
5027

5128
{#if value != null}
@@ -54,29 +31,9 @@
5431
<svg src="images/icons/trash-2.svg" class="icon" />
5532
<span class="text">delete</span>
5633
</Button>
57-
{:else if promise != null}
58-
{#await promise.then(resp => resp.json()).catch(() => promise = null)}
59-
<div class="loading-wrapper shadow-1">
60-
<img
61-
src={blobURL || '/images/create-project/placeholder.svg'}
62-
alt="Project image"
63-
/>
64-
<div class="status-overlay">
65-
<div class="lds-ellipsis">
66-
<div></div><div></div><div></div><div></div>
67-
</div>
68-
</div>
69-
</div>
70-
{:then image}
71-
<img src="{API_HOST_BROWSER}/file/{image.id}" alt="Project image" class="shadow-1 mr" />
72-
<Button isDanger on:click={clearValue} tooltip="Delete image">
73-
<svg src="images/icons/trash-2.svg" class="icon" />
74-
<span class="text">delete</span>
75-
</Button>
76-
{/await}
7734
{:else}
7835
<div class="options">
79-
<FileInput on:change={handleImageUpload} accept="image/*" />
36+
<FileInput on:change={validateUpload} accept="image/*" />
8037
{#if error}
8138
<p class="error">{error}</p>
8239
{/if}

src/containers/projects/new/step-0.svelte

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script>
22
import { createEventDispatcher } from 'svelte';
33
import Button from 'ui/button.svelte';
4+
import Dropdown from 'ui/dropdown.svelte';
45
import DraftCard from '@/components/projects/new/draft-card.svelte';
56
import {
67
getBlankProject,
@@ -34,6 +35,12 @@
3435
<div class="subtitle">
3536
Unleash your creative genius — we're here to help you!
3637
</div>
38+
<div class="advice">
39+
<Dropdown label="what is a project?">
40+
A project could be an event, an olympiad or any other volunteering opportunity. <br />
41+
Create some activities and have volunteers apply to them.
42+
</Dropdown>
43+
</div>
3744
<img class="illustration" src="/images/create-project/create-project.svg" alt="" />
3845
</div>
3946

src/containers/projects/new/step-1.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
<span class="lb">This is shown on the project card to catch attention.</span>
5353
<span class="lb">Best to use 16:9 photos.</span>
5454
</span>
55-
<ProjectImagePicker bind:value={$project.image_id} />
55+
<ProjectImagePicker bind:value={$project.image_id} on:resize-image />
5656
</FormField>
5757

5858
<FormField
@@ -63,7 +63,7 @@
6363
error={($project.organizer === '' && "The organizer field must not be empty.") || null}
6464
>
6565
<span slot="subtitle" class="desc">
66-
Name of the organizing department or individual.
66+
Give the volunteers a brief idea of who's behind this project (department or individual).
6767
</span>
6868
<TextField
6969
id="organizer"

src/routes/projects/new.svelte

+42
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import StepOne from '@/containers/projects/new/step-1.svelte';
2424
import StepTwo from '@/containers/projects/new/step-2.svelte';
2525
import StepThree from '@/containers/projects/new/step-3.svelte';
26+
import ImageResizer from '@/components/common/image-resizer.svelte';
2627
import * as api from '@/utils/api.js';
2728
import generateQueryString from '@/utils/generate-query-string.js';
2829
import {
@@ -54,6 +55,39 @@
5455
}
5556
});
5657
58+
const imageResizer = {
59+
open: false,
60+
image: null,
61+
file: null,
62+
error: null,
63+
show({ detail: file }) {
64+
imageResizer.open = true;
65+
imageResizer.file = file;
66+
imageResizer.image = URL.createObjectURL(file, { type: file.type });
67+
},
68+
async uploadImage({ detail: pixels }) {
69+
const formData = new FormData();
70+
formData.append('file', imageResizer.file);
71+
formData.append('x', pixels.x);
72+
formData.append('y', pixels.y);
73+
formData.append('width', pixels.width);
74+
formData.append('height', pixels.height);
75+
76+
try {
77+
const resp = await api.json(api.post('/file', {
78+
data: formData,
79+
}));
80+
81+
$project.image_id = resp.id;
82+
imageResizer.error = null;
83+
imageResizer.open = false;
84+
} catch (e) {
85+
console.error(e);
86+
imageResizer.error = 'Upload failed. Try again.';
87+
}
88+
},
89+
};
90+
5791
function goToStep(stepIdx) {
5892
goto(`/projects/new?step=${stepIdx}`);
5993
}
@@ -244,6 +278,7 @@
244278
{project}
245279
{duplicateName}
246280
{autosaved}
281+
on:resize-image={imageResizer.show}
247282
/>
248283
{:else if step === 2}
249284
<StepTwo
@@ -261,4 +296,11 @@
261296
/>
262297
{/if}
263298
</div>
299+
<ImageResizer
300+
aspectRatio={16/9}
301+
image={imageResizer.image}
302+
error={imageResizer.error}
303+
bind:isOpen={imageResizer.open}
304+
on:image-cropped={imageResizer.uploadImage}
305+
/>
264306
</Layout>

static/css/bundles/projects-id-edit.min.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

static/css/bundles/projects-new.min.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

static/css/create-project/steps.scss

+11
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ h2 {
4747
font-size: .9rem;
4848
}
4949

50+
.advice {
51+
margin-left: -1em;
52+
margin-top: .5em;
53+
54+
.relative-wrapper {
55+
padding: 1em;
56+
max-width: 20em;
57+
font-weight: 300;
58+
}
59+
}
60+
5061
.tablet {
5162
display: none;
5263
}

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -4519,6 +4519,11 @@ supports-hyperlinks@^2.0.0:
45194519
has-flag "^4.0.0"
45204520
supports-color "^7.0.0"
45214521

4522+
svelte-easy-crop@^1.0.3:
4523+
version "1.0.3"
4524+
resolved "https://registry.yarnpkg.com/svelte-easy-crop/-/svelte-easy-crop-1.0.3.tgz#3a5bd36320b5316b9ccf36c78b251a4f6fbb44a1"
4525+
integrity sha512-Jo0pZdnR43PVVldf28+MT50DD7LVY4rtnPNRF4Xuy5uiHgz+ixq9cqTK+/u5xbHs4cObzCbEbibzSRbAWObnJA==
4526+
45224527
svelte@^3.18.0:
45234528
version "3.20.1"
45244529
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.20.1.tgz#8417fcd883a2f534b642a0737368272e651cf3ac"

0 commit comments

Comments
 (0)