@nextcloud/image-editor 1.0.0-beta.1 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,10 @@ unmaintained Filerobot editor.
10
10
  - 📖 [API documentation](https://nextcloud-libraries.github.io/nextcloud-image-editor/)
11
11
  - 🎨 [Live demo](https://nextcloud-libraries.github.io/nextcloud-image-editor/demo/)
12
12
 
13
+ <!-- Absolute so it renders on npmjs too, where relative paths point
14
+ at a package that ships only dist/ -->
15
+ [![The editor in crop mode](https://raw.githubusercontent.com/nextcloud-libraries/nextcloud-image-editor/main/.github/screenshot.jpg)](https://nextcloud-libraries.github.io/nextcloud-image-editor/demo/)
16
+
13
17
  ## Features
14
18
 
15
19
  - Crop with rule-of-thirds guides, aspect presets (free, original,
@@ -64,18 +68,40 @@ stylesheet, so any bundler that handles CSS imports from dependencies
64
68
  picks them up. Where yours does not, load them yourself from the
65
69
  `@nextcloud/image-editor/style` export.
66
70
 
71
+ That import is also why the package has to go through a bundler. Loading
72
+ it in a plain Node process, for server-side rendering or a script, fails
73
+ with a syntax error as Node tries to parse the stylesheet as JavaScript.
74
+ This matches `@nextcloud/vue`, which imports its own CSS the same way,
75
+ and every Nextcloud app bundles with vite or webpack. See #5.
76
+
67
77
  ```vue
68
78
  <script setup lang="ts">
69
79
  import type { ExportResult } from '@nextcloud/image-editor'
80
+ import { ref } from 'vue'
70
81
  import { ImageEditor } from '@nextcloud/image-editor'
71
82
 
72
- function onSave({ blob, mimeType }: ExportResult) {
73
- // persist the blob, e.g. via @nextcloud/upload or WebDAV PUT
83
+ const saving = ref(false)
84
+
85
+ async function onSave({ blob, mimeType }: ExportResult) {
86
+ // The editor knows when it handed the blob over, not when the
87
+ // upload finished, so tell it: on a photo the upload is the larger
88
+ // half of the wait
89
+ saving.value = true
90
+ try {
91
+ // persist the blob, e.g. via @nextcloud/upload or WebDAV PUT
92
+ } finally {
93
+ saving.value = false
94
+ }
74
95
  }
75
96
  </script>
76
97
 
77
98
  <template>
78
- <ImageEditor :src="file" @save="onSave" @cancel="close" @error="showError" />
99
+ <ImageEditor
100
+ :src="file"
101
+ :saving="saving"
102
+ @save="onSave"
103
+ @cancel="close"
104
+ @error="showError" />
79
105
  </template>
80
106
  ```
81
107
 
@@ -89,6 +115,7 @@ function onSave({ blob, mimeType }: ExportResult) {
89
115
  | `label` | `string` | Accessible label of the canvas area. |
90
116
  | `exportOptions` | `ExportOptions` | `format`, `quality` and `maxSize` for the save button. Defaults to PNG at natural resolution. |
91
117
  | `initialState` | `EditorState` | State to open with, as emitted by `change`, for resuming an unfinished edit. Read when the source loads. |
118
+ | `saving` | `boolean` | Raise while your app stores the saved image. The editor shows the same progress it shows for its own export, so one indicator covers the whole wait. |
92
119
 
93
120
  | Event | Payload | Description |
94
121
  |-------|---------|-------------|
@@ -99,50 +99,51 @@
99
99
  padding: var(--default-grid-baseline) 2px;
100
100
  font-size: 10px;
101
101
  }
102
- }.adjust-panel[data-v-d7f848cf] {
102
+ }.adjust-panel[data-v-3ad63bd8] {
103
103
  display: flex;
104
104
  flex-direction: column;
105
105
  align-items: center;
106
106
  gap: calc(var(--default-grid-baseline) * 2);
107
107
  width: 100%;
108
108
  }
109
- .adjust-panel__tabs[data-v-d7f848cf] {
109
+ .adjust-panel__tabs[data-v-3ad63bd8] {
110
110
  display: flex;
111
111
  align-items: center;
112
112
  justify-content: center;
113
+ flex-wrap: wrap;
113
114
  gap: calc(var(--default-grid-baseline) * 2);
114
- }.annotate-panel[data-v-1bf135e4] {
115
+ }.annotate-panel[data-v-f7e8a194] {
115
116
  display: flex;
116
117
  flex-direction: column;
117
118
  align-items: center;
118
119
  gap: calc(var(--default-grid-baseline) * 2);
119
120
  width: 100%;
120
121
  }
121
- .annotate-panel__row[data-v-1bf135e4] {
122
+ .annotate-panel__row[data-v-f7e8a194] {
122
123
  display: flex;
123
124
  align-items: center;
124
125
  justify-content: center;
125
126
  flex-wrap: wrap;
126
127
  gap: calc(var(--default-grid-baseline) * 2);
127
128
  }
128
- .annotate-panel__divider[data-v-1bf135e4] {
129
+ .annotate-panel__divider[data-v-f7e8a194] {
129
130
  width: 1px;
130
131
  height: 24px;
131
132
  background-color: rgba(255, 255, 255, 0.12);
132
133
  }
133
- .annotate-panel__option[data-v-1bf135e4] {
134
+ .annotate-panel__option[data-v-f7e8a194] {
134
135
  display: flex;
135
136
  align-items: center;
136
137
  gap: var(--default-grid-baseline);
137
138
  font-size: 12px;
138
139
  opacity: 0.9;
139
140
  }
140
- .annotate-panel__dot[data-v-1bf135e4] {
141
+ .annotate-panel__dot[data-v-f7e8a194] {
141
142
  display: block;
142
143
  border-radius: 50%;
143
144
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.5);
144
145
  }
145
- .annotate-panel__glyph[data-v-1bf135e4] {
146
+ .annotate-panel__glyph[data-v-f7e8a194] {
146
147
  font-family: Helvetica, Arial, sans-serif;
147
148
  line-height: 1;
148
149
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
@@ -269,13 +270,13 @@
269
270
  align-items: center;
270
271
  gap: calc(var(--default-grid-baseline) * 2);
271
272
  padding: var(--default-grid-baseline);
272
- }.editor-topbar[data-v-9ba9bd23] {
273
+ }.editor-topbar[data-v-50392429] {
273
274
  display: flex;
274
275
  align-items: center;
275
276
  justify-content: space-between;
276
277
  padding: calc(var(--default-grid-baseline) * 2) calc(var(--default-grid-baseline) * 6);
277
278
  }
278
- .editor-topbar__history[data-v-9ba9bd23] {
279
+ .editor-topbar__history[data-v-50392429] {
279
280
  display: flex;
280
281
  align-items: center;
281
282
  gap: 2px;
@@ -285,12 +286,12 @@
285
286
  backdrop-filter: blur(24px) saturate(1.4);
286
287
  border: 1px solid rgba(255, 255, 255, 0.09);
287
288
  }
288
- .editor-topbar__separator[data-v-9ba9bd23] {
289
+ .editor-topbar__separator[data-v-50392429] {
289
290
  width: 1px;
290
291
  height: 20px;
291
292
  background-color: var(--color-border);
292
293
  }
293
- .editor-topbar__zoom[data-v-9ba9bd23] {
294
+ .editor-topbar__zoom[data-v-50392429] {
294
295
  min-width: 48px;
295
296
  border: none;
296
297
  background: transparent;
@@ -301,40 +302,40 @@
301
302
  cursor: pointer;
302
303
  padding: 0 4px;
303
304
  }
304
- .editor-topbar__zoom[data-v-9ba9bd23]:hover:not(:disabled) {
305
+ .editor-topbar__zoom[data-v-50392429]:hover:not(:disabled) {
305
306
  opacity: 1;
306
307
  }
307
- .editor-topbar__zoom[data-v-9ba9bd23]:focus-visible {
308
+ .editor-topbar__zoom[data-v-50392429]:focus-visible {
308
309
  outline: 2px solid var(--color-primary-element);
309
310
  outline-offset: 2px;
310
311
  }
311
- .editor-topbar__zoom[data-v-9ba9bd23]:disabled {
312
+ .editor-topbar__zoom[data-v-50392429]:disabled {
312
313
  cursor: default;
313
314
  opacity: 0.4;
314
315
  }
315
- .editor-topbar__spacer[data-v-9ba9bd23], .editor-topbar__actions[data-v-9ba9bd23] {
316
+ .editor-topbar__spacer[data-v-50392429], .editor-topbar__actions[data-v-50392429] {
316
317
  flex: 1;
317
318
  display: flex;
318
319
  align-items: center;
319
320
  gap: var(--default-grid-baseline);
320
321
  }
321
- .editor-topbar__actions[data-v-9ba9bd23] {
322
+ .editor-topbar__actions[data-v-50392429] {
322
323
  justify-content: flex-end;
323
324
  }
324
- .editor-topbar__cancel-icon[data-v-9ba9bd23] {
325
+ .editor-topbar__cancel-icon[data-v-50392429] {
325
326
  display: none !important;
326
327
  }
327
328
  @container editor (max-width: 600px) {
328
- .editor-topbar[data-v-9ba9bd23] {
329
+ .editor-topbar[data-v-50392429] {
329
330
  padding-inline: calc(var(--default-grid-baseline) * 2);
330
331
  }
331
- .editor-topbar__cancel-text[data-v-9ba9bd23] {
332
+ .editor-topbar__cancel-text[data-v-50392429] {
332
333
  display: none !important;
333
334
  }
334
- .editor-topbar__cancel-icon[data-v-9ba9bd23] {
335
+ .editor-topbar__cancel-icon[data-v-50392429] {
335
336
  display: inline-flex !important;
336
337
  }
337
- .editor-topbar__zoom[data-v-9ba9bd23] {
338
+ .editor-topbar__zoom[data-v-50392429] {
338
339
  min-width: 40px;
339
340
  }
340
341
  }
@@ -366,7 +367,26 @@
366
367
  white-space: pre;
367
368
  z-index: 1;
368
369
  }
369
- .image-editor[data-v-e3346ccd] {
370
+ /**
371
+ * Nextcloud's own values for these arrive from the server stylesheet,
372
+ * and @nextcloud/vue reads them with no fallback of its own. Declaring
373
+ * them on the editor root would override the host, which is exactly
374
+ * what made every control here 10px larger than the surrounding
375
+ * interface. Registering them instead supplies an initial value only
376
+ * where nothing else defines one, and lets the host's declaration win
377
+ * wherever there is one.
378
+ */
379
+ @property --default-clickable-area {
380
+ syntax: "<length>";
381
+ inherits: true;
382
+ initial-value: 34px;
383
+ }
384
+ @property --default-grid-baseline {
385
+ syntax: "<length>";
386
+ inherits: true;
387
+ initial-value: 4px;
388
+ }
389
+ .image-editor[data-v-5225c517] {
370
390
  --color-main-text: #f2f2f7;
371
391
  --color-main-background: #141416;
372
392
  --color-background-hover: rgba(255, 255, 255, 0.08);
@@ -374,8 +394,6 @@
374
394
  --color-border: rgba(255, 255, 255, 0.09);
375
395
  --color-element-hover: rgba(255, 255, 255, 0.08);
376
396
  --editor-glass: rgba(20, 20, 26, 0.6);
377
- --default-clickable-area: 44px;
378
- --default-grid-baseline: 4px;
379
397
  font-size: 13px;
380
398
  position: relative;
381
399
  height: 100%;
@@ -385,15 +403,15 @@
385
403
  background-color: var(--color-main-background);
386
404
  container: editor/size;
387
405
  }
388
- .image-editor[data-v-e3346ccd], .image-editor[data-v-e3346ccd] *, .image-editor[data-v-e3346ccd] *::before, .image-editor[data-v-e3346ccd] *::after {
406
+ .image-editor[data-v-5225c517], .image-editor[data-v-5225c517] *, .image-editor[data-v-5225c517] *::before, .image-editor[data-v-5225c517] *::after {
389
407
  box-sizing: border-box;
390
408
  }
391
- .image-editor__shell[data-v-e3346ccd] {
409
+ .image-editor__shell[data-v-5225c517] {
392
410
  position: relative;
393
411
  height: 100%;
394
412
  width: 100%;
395
413
  }
396
- .image-editor[data-v-e3346ccd]::before {
414
+ .image-editor[data-v-5225c517]::before {
397
415
  content: "";
398
416
  position: absolute;
399
417
  inset: -10%;
@@ -403,7 +421,7 @@
403
421
  filter: blur(64px) saturate(1.3) brightness(0.55);
404
422
  transform: scale(1.15);
405
423
  }
406
- .image-editor__frame[data-v-e3346ccd] {
424
+ .image-editor__frame[data-v-5225c517] {
407
425
  position: relative;
408
426
  height: 100%;
409
427
  width: 100%;
@@ -411,23 +429,23 @@
411
429
  background: rgba(14, 14, 18, 0.55);
412
430
  backdrop-filter: blur(40px);
413
431
  }
414
- .image-editor__viewport[data-v-e3346ccd] {
432
+ .image-editor__viewport[data-v-5225c517] {
415
433
  position: absolute;
416
434
  inset: 0;
417
435
  padding: 56px 16px 16px;
418
436
  }
419
437
  @container editor (max-width: 600px) {
420
- .image-editor__viewport[data-v-e3346ccd] {
438
+ .image-editor__viewport[data-v-5225c517] {
421
439
  padding: 56px 8px 8px;
422
440
  }
423
- .image-editor .image-editor__controls[data-v-e3346ccd] {
441
+ .image-editor .image-editor__controls[data-v-5225c517] {
424
442
  inset-inline: 8px;
425
443
  transform: none;
426
444
  min-width: 0;
427
445
  max-width: none;
428
446
  width: auto;
429
447
  }
430
- .image-editor .image-editor__strip[data-v-e3346ccd] {
448
+ .image-editor .image-editor__strip[data-v-5225c517] {
431
449
  flex-direction: row;
432
450
  inset-inline: 8px;
433
451
  inset-block-start: auto;
@@ -437,7 +455,7 @@
437
455
  overflow-x: auto;
438
456
  overflow-y: hidden;
439
457
  }
440
- .image-editor .image-editor__rail[data-v-e3346ccd] {
458
+ .image-editor .image-editor__rail[data-v-5225c517] {
441
459
  inset-inline-start: var(--default-grid-baseline);
442
460
  inset-block-start: 64px;
443
461
  transform: none;
@@ -450,49 +468,49 @@
450
468
  border-radius: var(--border-radius-large, 12px);
451
469
  }
452
470
  }
453
- .image-editor__canvas[data-v-e3346ccd] {
471
+ .image-editor__canvas[data-v-5225c517] {
454
472
  height: 100%;
455
473
  width: 100%;
456
474
  touch-action: none;
457
475
  }
458
- .image-editor__topbar[data-v-e3346ccd] {
476
+ .image-editor__topbar[data-v-5225c517] {
459
477
  position: absolute;
460
478
  inset-block-start: 0;
461
479
  inset-inline: 0;
462
480
  background: linear-gradient(rgba(8, 8, 12, 0.5), transparent);
463
481
  }
464
- .image-editor__rail[data-v-e3346ccd] {
482
+ .image-editor__rail[data-v-5225c517] {
465
483
  position: absolute;
466
484
  inset-inline-start: calc(var(--default-grid-baseline) * 4);
467
485
  inset-block-start: 50%;
468
486
  transform: translateY(-50%);
469
487
  }
470
- .image-editor__controls[data-v-e3346ccd] {
488
+ .image-editor__controls[data-v-5225c517] {
471
489
  position: absolute;
472
490
  inset-block-end: calc(var(--default-grid-baseline) * 5);
473
491
  inset-inline-start: 50%;
474
492
  transform: translateX(-50%);
475
493
  }
476
- .image-editor__strip[data-v-e3346ccd] {
494
+ .image-editor__strip[data-v-5225c517] {
477
495
  position: absolute;
478
496
  inset-inline-end: calc(var(--default-grid-baseline) * 4);
479
497
  inset-block-start: 50%;
480
498
  transform: translateY(-50%);
481
499
  max-height: calc(100% - 160px);
482
500
  }
483
- .image-editor .hidden-visually[data-v-e3346ccd] {
501
+ .image-editor .hidden-visually[data-v-5225c517] {
484
502
  position: absolute;
485
503
  width: 1px;
486
504
  height: 1px;
487
505
  overflow: hidden;
488
506
  clip-path: inset(50%);
489
507
  }
490
- .image-editor__loading[data-v-e3346ccd] {
508
+ .image-editor__loading[data-v-5225c517] {
491
509
  position: absolute;
492
510
  inset: 0;
493
511
  margin: auto;
494
512
  }
495
- .image-editor__error[data-v-e3346ccd] {
513
+ .image-editor__error[data-v-5225c517] {
496
514
  position: absolute;
497
515
  inset: 0;
498
516
  display: flex;
@@ -503,7 +521,7 @@
503
521
  text-align: center;
504
522
  padding: calc(var(--default-grid-baseline) * 4);
505
523
  }
506
- .image-editor__error p[data-v-e3346ccd] {
524
+ .image-editor__error p[data-v-5225c517] {
507
525
  margin: 0;
508
526
  opacity: 0.85;
509
527
  }