@lancom/shared 0.0.461 → 0.0.463

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.
@@ -329,6 +329,9 @@ export default {
329
329
  importProductImagesJson(productId, images) {
330
330
  return _post(`admin/products/${productId}/import-images-json`, { images });
331
331
  },
332
+ importProductDocumentsJson(productId, documents) {
333
+ return _post(`admin/products/${productId}/import-documents-json`, { documents });
334
+ },
332
335
  fetchProductsKits(params) {
333
336
  return _get('admin/products-kit', params);
334
337
  },
@@ -178,6 +178,16 @@ const api = {
178
178
  }
179
179
  return _post(requestUrl, formData, config);
180
180
  },
181
+ uploadFile(file, progressCallback = Function.prototype, requestUrl = 'file/upload') {
182
+ const config = {
183
+ onUploadProgress: ({ loaded, total }) => {
184
+ progressCallback(Math.round((loaded * 100) / total));
185
+ }
186
+ };
187
+ const formData = new FormData();
188
+ formData.set('file', file);
189
+ return _post(requestUrl, formData, config);
190
+ },
181
191
  sendFailedConversionFile(data, shop) {
182
192
  return _post(`shop/${shop}/image/send-failed-conversion`, data);
183
193
  },
@@ -66,7 +66,7 @@ export class TextLayer extends Layer {
66
66
  export class ArtLayer extends Layer {
67
67
  url = null;
68
68
  fileName = '';
69
- editableDetails = true;
69
+ editableDetails = false;
70
70
  scaleX = 1;
71
71
  scaleY = 1;
72
72
  width = null;
@@ -8,7 +8,8 @@ class Breakpoints {
8
8
  sm: 768,
9
9
  md: 1024,
10
10
  lg: 1280,
11
- xl: 1600
11
+ xl: 1600,
12
+ xxl: 1800
12
13
  };
13
14
 
14
15
  constructor() {
@@ -42,6 +43,8 @@ class Breakpoints {
42
43
  return 'lg';
43
44
  } else if (this.isXl(w)) {
44
45
  return 'xl';
46
+ } else if (this.isXXl(w)) {
47
+ return 'xxl';
45
48
  } else {
46
49
  return 'all';
47
50
  }
@@ -69,7 +72,11 @@ class Breakpoints {
69
72
  }
70
73
 
71
74
  isXl(val) {
72
- return val >= this.screens.xl;
75
+ return val >= this.screens.xl && val < this.screens.xxl;
76
+ }
77
+
78
+ isXXl(val) {
79
+ return val >= this.screens.xxl;
73
80
  }
74
81
  }
75
82
 
@@ -21,6 +21,30 @@ export function loadCenterImage(cb = Function.prototype) {
21
21
  };
22
22
  }
23
23
 
24
+ export function autoFitObjectToPrintArea(target, fabricHelper) {
25
+ if (fabricHelper && fabricHelper.printAreaRect) {
26
+ const printArea = fabricHelper.printAreaRect;
27
+ const scaleX = printArea.width / target.width;
28
+ const scaleY = printArea.height / target.height;
29
+ const scale = Math.min(scaleX, scaleY) * 0.8;
30
+
31
+ target.set({
32
+ scaleX: scale,
33
+ scaleY: scale,
34
+ left: printArea.left + printArea.width / 2,
35
+ top: printArea.top + printArea.height / 2
36
+ });
37
+
38
+ target.setCoords();
39
+ fabricHelper.editor.renderAll();
40
+ fabricHelper.dispatch('setField', { field: 'scaleX', value: scale });
41
+ fabricHelper.dispatch('setField', { field: 'scaleY', value: scale });
42
+ fabricHelper.dispatch('setField', { field: 'left', value: target.left });
43
+ fabricHelper.dispatch('setField', { field: 'top', value: target.top });
44
+ fabricHelper.dispatch('setField', { field: 'boundingRect', value: target.getBoundingRect() });
45
+ }
46
+ }
47
+
24
48
  function renderIcon(ctx, left, top, styleOverride, fabricObject, icon) {
25
49
  const size = 20;
26
50
  ctx.save();
@@ -111,27 +135,7 @@ export function setupCustomControls(fabricHelper) {
111
135
  const target = transform.target;
112
136
  const helper = fabricHelper;
113
137
 
114
- if (helper && helper.printAreaRect) {
115
- const printArea = helper.printAreaRect;
116
- const scaleX = printArea.width / target.width;
117
- const scaleY = printArea.height / target.height;
118
- const scale = Math.min(scaleX, scaleY) * 0.8;
119
-
120
- target.set({
121
- scaleX: scale,
122
- scaleY: scale,
123
- left: printArea.left + printArea.width / 2,
124
- top: printArea.top + printArea.height / 2
125
- });
126
-
127
- target.setCoords();
128
- helper.editor.renderAll();
129
- helper.dispatch('setField', { field: 'scaleX', value: scale });
130
- helper.dispatch('setField', { field: 'scaleY', value: scale });
131
- helper.dispatch('setField', { field: 'left', value: target.left });
132
- helper.dispatch('setField', { field: 'top', value: target.top });
133
- helper.dispatch('setField', { field: 'boundingRect', value: target.getBoundingRect() });
134
- }
138
+ autoFitObjectToPrintArea(target, helper);
135
139
 
136
140
  return true;
137
141
  },
@@ -1,7 +1,7 @@
1
1
 
2
2
  import { fabric } from 'fabric';
3
3
  import { getPrintAreaByName } from '../models/print-area';
4
- import { loadRotateImage, loadCenterImage, setupCustomControls } from './fabric/selection-style';
4
+ import { loadRotateImage, loadCenterImage, setupCustomControls, autoFitObjectToPrintArea } from './fabric/selection-style';
5
5
  import EventDispatcher from './event-dispatcher';
6
6
  import { createText, createArt } from './fabric/object-factory';
7
7
  import { buildWireframe } from './fabric/wireframe';
@@ -169,6 +169,13 @@ export default class FabricHelper {
169
169
  });
170
170
  }
171
171
 
172
+ autoFitActiveObject() {
173
+ const activeObject = this.editor.getActiveObject();
174
+ if (activeObject) {
175
+ autoFitObjectToPrintArea(activeObject, this);
176
+ }
177
+ }
178
+
172
179
  getEditorDPI() {
173
180
  const { widthInInches, width } = this.printAreaRect;
174
181
  return width / widthInInches;
@@ -203,7 +210,7 @@ export default class FabricHelper {
203
210
  };
204
211
  this[methods[layer.type]]({ layer, initial }).then(obj => {
205
212
  // obj.clipPath = this.boundingBox;
206
- console.log('obj: ', obj.top, obj.left);
213
+ // console.log('obj: ', obj.top, obj.left);
207
214
  this.handleListeners(obj, layer.type);
208
215
  this.editor.add(obj);
209
216
  if (active) {
@@ -287,9 +294,11 @@ export default class FabricHelper {
287
294
  });
288
295
 
289
296
  object.on('modified', () => {
297
+ const boundingRect = object.getBoundingRect();
290
298
  this.dispatch('setField', { field: 'top', value: Math.round(object.top) });
291
299
  this.dispatch('setField', { field: 'left', value: Math.round(object.left) });
292
- this.dispatch('setField', { field: 'boundingRect', value: object.getBoundingRect() });
300
+ this.dispatch('setField', { field: 'boundingRect', value: boundingRect });
301
+ this.dispatch('checkPrintAreaChange', { top: object.top, left: object.left, boundingRect });
293
302
  });
294
303
  /*
295
304
  ** TEXT OBJECT EVENTS
@@ -1,7 +1,7 @@
1
1
  <script>
2
2
  import Breakpoints from '@lancom/shared/assets/js/utils/breakpoints';
3
3
 
4
- const nameOptions = ['xs', 'sm', 'md', 'lg', 'xl'];
4
+ const nameOptions = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
5
5
  const modeOptions = ['up', 'down', 'only'];
6
6
 
7
7
  export default {
@@ -54,6 +54,7 @@ export default {
54
54
  hasUploadLink: { type: Boolean, default: false },
55
55
  hasConversionErrorModal: { type: Boolean, default: true },
56
56
  showErrorMessage: { type: Boolean, default: true },
57
+ checkStaticUrl: { type: Boolean, default: true },
57
58
  url: { type: String },
58
59
  accept: { type: String, default: 'all' }
59
60
  },
@@ -116,7 +117,7 @@ export default {
116
117
  try {
117
118
  const image = await api.uploadImage(this.file, this.onProgress, this.url, this.convert);
118
119
  this.onProgress(0);
119
- const url = image && staticLink(image.thumb || image.origin);
120
+ const url = this.checkStaticUrl ? (image && staticLink(image.thumb || image.origin)) : image?.url;
120
121
  this.$emit('onuploaded', { ...image, url });
121
122
  } catch (e) {
122
123
  console.dir(e);
@@ -35,6 +35,9 @@
35
35
  align-items: center;
36
36
  justify-content: center;
37
37
  }
38
+ &-item {
39
+ border-bottom: 2px solid $grey_3;
40
+ }
38
41
  }
39
42
  &__empty {
40
43
  width: 306px;
@@ -69,13 +69,15 @@
69
69
  :key="group.printArea._id"
70
70
  class="EditorLayersGroup__wrapper">
71
71
  <div
72
- @click="toggleOpenedGroup(group.printArea)"
73
- class="EditorLayersGroup__header">
72
+ class="EditorLayersGroup__header"
73
+ @click="toggleOpenedGroup(group.printArea)">
74
74
  <div>
75
75
  <h5 class="lc_h5">
76
76
  {{ group | groupLabel }}
77
77
  </h5>
78
- <div class="lc_caption">{{ group.printArea.side }}</div>
78
+ <div class="lc_caption">
79
+ {{ group.printArea.side }}
80
+ </div>
79
81
  </div>
80
82
  <div
81
83
  class="EditorLayersGroup__arrow"
@@ -84,8 +86,8 @@
84
86
  </div>
85
87
  </div>
86
88
  <div
87
- class="EditorLayers__list-content"
88
- v-if="openedGroup === group.printArea._id">
89
+ v-if="openedGroup === group.printArea._id"
90
+ class="EditorLayers__list-content">
89
91
  <editor-layers-layer
90
92
  v-for="(layer, index) in group.layers"
91
93
  :key="`${layer.createdAt}-${index}`"
@@ -93,12 +95,18 @@
93
95
  :active="selectedLayer && selectedLayer.createdAt === layer.createdAt"
94
96
  :visible-move-up="layer !== group.layers[0]"
95
97
  :visible-move-down="layer !== group.layers[group.layers.length - 1]"
98
+ :layers="editableLayers"
99
+ :side="group.printArea.side"
100
+ class="EditorLayers__list-item"
96
101
  @select="setSelectedLayer(layer)"
97
102
  @edit="editLayer(layer)"
98
103
  @duplicate="duplicateLayer(layer)"
99
104
  @remove="removeLayer(layer)"
100
105
  @up="moveUp(layer)"
101
- @down="moveDown(layer)">
106
+ @down="moveDown(layer)"
107
+ @print-area-select="handleLayerPrintAreaSelect"
108
+ @print-area-mouseover="toogleBoundBox(true, $event)"
109
+ @print-area-mouseleave="toogleBoundBox(false, $event)"">
102
110
  </editor-layers-layer>
103
111
  </div>
104
112
  </div>
@@ -219,7 +227,8 @@ export default {
219
227
  'setEditableSide',
220
228
  'setSelectedPrintArea',
221
229
  'setEditablePrintArea',
222
- 'setPreviewPrintArea'
230
+ 'setPreviewPrintArea',
231
+ 'triggerAutoFit'
223
232
  ]),
224
233
  ...mapActions(['increaseLayersUpdatesCount']),
225
234
  openSelectedLayerGroup(selected) {
@@ -248,8 +257,8 @@ export default {
248
257
  }
249
258
  },
250
259
  editLayer(layer) {
260
+ this.setSelectedLayer(layer);
251
261
  if (layer.editableDetails) {
252
- this.setSelectedLayer(layer);
253
262
  this.setEditModeSelectedLayer(true);
254
263
  }
255
264
  },
@@ -287,6 +296,24 @@ export default {
287
296
  const printSize = printArea.printSize._id;
288
297
  this.setLayerField({ field: 'printSize', value: printSize });
289
298
 
299
+ this.triggerAutoFit();
300
+
301
+ this.toggleOpenedGroup(printArea, true);
302
+ },
303
+ handleLayerPrintAreaSelect({ layer, option }) {
304
+ // Set the layer as selected first
305
+ this.setSelectedLayer(layer);
306
+
307
+ // Update the layer's print area
308
+ const printArea = option.printArea;
309
+ this.setLayerField({ field: 'printArea', value: printArea.parentPrintArea || printArea._id });
310
+
311
+ const printSize = printArea.printSize._id;
312
+ this.setLayerField({ field: 'printSize', value: printSize });
313
+
314
+ this.triggerAutoFit();
315
+
316
+ // Open the group where this layer now belongs
290
317
  this.toggleOpenedGroup(printArea, true);
291
318
  },
292
319
  toogleBoundBox(state, option) {
@@ -3,13 +3,7 @@
3
3
 
4
4
  .EditorLayersLayer {
5
5
  &__wrapper {
6
- padding: 30px 0px 20px 20px;
7
- display: flex;
8
- position: relative;
9
- cursor: pointer;
10
- user-select: none;
11
- align-items: stretch;
12
- border-bottom: 2px solid $grey_3;
6
+ padding: 20px 0px 20px 20px;
13
7
  position: relative;
14
8
  &:before {
15
9
  content: '';
@@ -31,6 +25,15 @@
31
25
  padding-left: 8px;
32
26
  }
33
27
  }
28
+ &__preview {
29
+ display: flex;
30
+ cursor: pointer;
31
+ user-select: none;
32
+ align-items: stretch;
33
+ }
34
+ &__print-areas {
35
+ margin-top: 20px;
36
+ }
34
37
  &__thumb {
35
38
  flex-shrink: 0;
36
39
  width: 83px;
@@ -157,3 +160,12 @@
157
160
  }
158
161
  }
159
162
  }
163
+
164
+ ::v-deep {
165
+ .EditorPrintAreaOptions__wrapper {
166
+ padding: 0 !important;
167
+ }
168
+ .EditorPrintAreaOptions__option {
169
+ margin-bottom: 0 !important;
170
+ }
171
+ }
@@ -1,99 +1,114 @@
1
1
  <template>
2
2
  <div
3
3
  class="EditorLayersLayer__wrapper"
4
- :class="{ active }"
5
- @click="$emit('select')">
6
- <div class="EditorLayersLayer__controls">
7
- <span v-if="visibleMoveUp">
4
+ :class="{ active }">
5
+ <div
6
+ class="EditorLayersLayer__preview"
7
+ @click="$emit('select')">
8
+ <div class="EditorLayersLayer__controls">
9
+ <span v-if="visibleMoveUp">
10
+ <i
11
+ class="icon-arrow-left EditorLayersLayer__up"
12
+ @click="$emit('up')"></i>
13
+ </span>
14
+ <span v-if="visibleMoveDown">
15
+ <i
16
+ class="icon-arrow-left EditorLayersLayer__down"
17
+ @click="$emit('down')"></i>
18
+ </span>
8
19
  <i
9
- class="icon-arrow-left EditorLayersLayer__up"
10
- @click="$emit('up')"></i>
11
- </span>
12
- <span v-if="visibleMoveDown">
20
+ class="icon-delete"
21
+ @click="$emit('remove')"></i>
22
+ </div>
23
+ <div class="EditorLayersLayer__thumb">
13
24
  <i
14
- class="icon-arrow-left EditorLayersLayer__down"
15
- @click="$emit('down')"></i>
16
- </span>
17
- <i
18
- class="icon-delete"
19
- @click="$emit('remove')"></i>
20
- </div>
21
- <div class="EditorLayersLayer__thumb">
22
- <i
23
- v-if="layerIcon"
24
- :class="layerIcon"></i>
25
- <div
26
- v-else-if="layer.type === 'art' && layer.url"
27
- :style="{ 'background-image': `url(${layer.url})` }"
28
- class="EditorLayersLayer__thumb-inner">
25
+ v-if="layerIcon"
26
+ :class="layerIcon"></i>
27
+ <div
28
+ v-else-if="layer.type === 'art' && layer.url"
29
+ :style="{ 'background-image': `url(${layer.url})` }"
30
+ class="EditorLayersLayer__thumb-inner">
31
+ </div>
29
32
  </div>
30
- </div>
31
- <div class="EditorLayersLayer__content">
32
- <!-- <div>
33
- <div v-if="layerPrintArea">layerPrintArea: {{ layerPrintArea.name }}</div>
34
- <div v-if="layerPrintType">layerPrintType: {{ layerPrintType.name }}</div>
35
- <div v-if="layerPrintSize">layerPrintSize: {{ layerPrintSize.name }}</div>
36
- <div v-if="side">side: {{ side }}</div>
37
- </div> -->
38
- <div class="EditorLayersLayer__info">
39
- <div class="EditorLayersLayer__info-row">
40
- <div class="EditorLayersLayer__badge">
41
- {{ layer.type }}
33
+ <div class="EditorLayersLayer__content">
34
+ <div class="EditorLayersLayer__info">
35
+ <div class="EditorLayersLayer__info-row">
36
+ <div class="EditorLayersLayer__badge">
37
+ {{ layer.type }}
38
+ </div>
42
39
  </div>
43
- </div>
44
- <div class="EditorLayersLayer__info-row">
45
- <div class="EditorLayersLayer__title">
46
- {{ title }}
40
+ <div class="EditorLayersLayer__info-row">
41
+ <div class="EditorLayersLayer__title">
42
+ {{ title }}
43
+ </div>
44
+ </div>
45
+ <div
46
+ v-if="artQuality"
47
+ class="EditorLayersLayer__info-row">
48
+ <div class="EditorLayersLayer__quality-label">
49
+ Print quality:
50
+ </div>
51
+ <div
52
+ class="EditorLayersLayer__quality-value"
53
+ :class="artQuality.class">
54
+ {{ artQuality.value || artQuality.class }}
55
+ </div>
47
56
  </div>
48
57
  </div>
49
- <div
50
- v-if="artQuality"
51
- class="EditorLayersLayer__info-row">
52
- <div class="EditorLayersLayer__quality-label">
53
- Print quality:
58
+ <div class="EditorLayersLayer__actions">
59
+ <div
60
+ v-if="layer.editableDetails"
61
+ class="lc_medium16 EditorLayersLayer__action"
62
+ @click.stop="$emit('edit')">
63
+ Edit
64
+ </div>
65
+ <div v-if="layer.editableDetails">
66
+ &nbsp;
67
+ &#124;
68
+ &nbsp;
54
69
  </div>
55
70
  <div
56
- class="EditorLayersLayer__quality-value"
57
- :class="artQuality.class">
58
- {{ artQuality.value || artQuality.class }}
71
+ class="lc_medium16 EditorLayersLayer__action"
72
+ @click.stop="$emit('duplicate')">
73
+ Duplicate
74
+ </div>
75
+ &nbsp;
76
+ &#124;
77
+ &nbsp;
78
+ <div
79
+ class="lc_medium16 EditorLayersLayer__action"
80
+ @click.stop="$emit('remove')">
81
+ Remove
59
82
  </div>
60
- </div>
61
- </div>
62
- <div class="EditorLayersLayer__actions">
63
- <div
64
- v-if="layer.editableDetails"
65
- class="lc_medium16 EditorLayersLayer__action"
66
- @click.stop="$emit('edit')">
67
- Edit
68
- </div>
69
- &nbsp;
70
- &#124;
71
- &nbsp;
72
- <div
73
- class="lc_medium16 EditorLayersLayer__action"
74
- @click.stop="$emit('duplicate')">
75
- Duplicate
76
- </div>
77
- &nbsp;
78
- &#124;
79
- &nbsp;
80
- <div
81
- class="lc_medium16 EditorLayersLayer__action"
82
- @click.stop="$emit('remove')">
83
- Remove
84
83
  </div>
85
84
  </div>
86
85
  </div>
86
+ <div
87
+ v-if="product && active"
88
+ class="EditorLayersLayer__print-areas">
89
+ <editor-print-area-options
90
+ :selected="layer.printArea"
91
+ :side="side"
92
+ :product="product"
93
+ :layers="layers"
94
+ @select="handlePrintAreaSelect"
95
+ @option-mouseover="$emit('print-area-mouseover', $event)"
96
+ @option-mouseleave="$emit('print-area-mouseleave', $event)" />
97
+ </div>
87
98
  </div>
88
99
  </template>
89
100
 
90
101
  <script>
91
102
  import printLayerMixin from '@lancom/shared/mixins/print-layer';
92
103
  import { number } from '@lancom/shared/assets/js/utils/filters';
104
+ import EditorPrintAreaOptions from '@lancom/shared/components/editor/editor_print_area_options/editor-print-area-options';
93
105
 
94
106
  export default {
95
107
  name: 'EditorLayersLayer',
96
108
  filters: { number },
109
+ components: {
110
+ EditorPrintAreaOptions
111
+ },
97
112
  mixins: [printLayerMixin],
98
113
  props: {
99
114
  active: {
@@ -105,6 +120,10 @@ export default {
105
120
  },
106
121
  visibleMoveDown: {
107
122
  type: Boolean
123
+ },
124
+ layers: {
125
+ type: Array,
126
+ default: () => ([])
108
127
  }
109
128
  },
110
129
  computed: {
@@ -132,16 +151,11 @@ export default {
132
151
  return null;
133
152
  }
134
153
  const isVector = /\.(ai|pdf|svg|eps)$/.test((this.layer.url || '').toLowerCase());
135
- let quality;
136
154
  switch (true) {
137
155
  case this.layer.dpi < 75:
138
- quality = 'too small';
139
- break;
140
156
  case this.layer.dpi >= 75 && this.layer.dpi < 100:
141
- quality = 'low quality';
142
157
  break;
143
158
  case this.layer.dpi >= 100 || isVector:
144
- quality = 'good quality';
145
159
  break;
146
160
  }
147
161
  return {
@@ -152,6 +166,11 @@ export default {
152
166
  },
153
167
  mounted() {
154
168
  console.log('layer: ', this.layer);
169
+ },
170
+ methods: {
171
+ handlePrintAreaSelect(option) {
172
+ this.$emit('print-area-select', { layer: this.layer, option });
173
+ }
155
174
  }
156
175
  };
157
176
  </script>
@@ -36,8 +36,9 @@
36
36
  :print-area-size="selectedPrintArea"
37
37
  :visible-background-image="visibleBackgroundImage"
38
38
  :class="side"
39
- class="EditorWorkspace__side rotate-y-element">
40
- </editor-workspace-side>
39
+ class="EditorWorkspace__side rotate-y-element"
40
+ @workspace="onWorkspaceChange($event)"
41
+ @print-area-changed="onPrintAreaChanged($event)" />
41
42
  </transition>
42
43
  </div>
43
44
  </div>
@@ -74,8 +75,8 @@
74
75
  :zoom-size="sideZoomSize"
75
76
  :visible-background-image="visibleBackgroundImage"
76
77
  class="EditorWorkspace__side rotate-y-element"
77
- @workspace="onWorkspaceChange($event)">
78
- </editor-workspace-side>
78
+ @workspace="onWorkspaceChange($event)"
79
+ @print-area-changed="onPrintAreaChanged($event)" />
79
80
  </transition>
80
81
  </div>
81
82
  <div
@@ -214,7 +215,6 @@ export default {
214
215
  const printAreaWidth = printAreaRect.width;
215
216
  const printAreaHeight = printAreaRect.height;
216
217
 
217
-
218
218
  const targetWidth = this.editorSize.width * 0.6;
219
219
  const scaleX = targetWidth / printAreaWidth;
220
220
  const scale = scaleX;
@@ -304,6 +304,9 @@ export default {
304
304
  if (this.$refs.editor) {
305
305
  this.$refs.editor.toogleBoundBox(state, printAreaOption);
306
306
  }
307
+ },
308
+ onPrintAreaChanged({ printArea, size }) {
309
+ this.selectPrintArea({ printArea, size });
307
310
  }
308
311
  }
309
312
  };
@@ -136,12 +136,12 @@ import { fabric } from 'fabric';
136
136
  import { mapGetters, mapMutations, mapActions } from 'vuex';
137
137
  import throttle from 'lodash.throttle';
138
138
  import debounce from 'lodash.debounce';
139
- import { getLargeColorImage, getMediumColorImage } from '@lancom/shared/assets/js/utils/colors';
139
+ import { getLargeColorImage, getMediumColorImage, getColorBackgroundStyle } from '@lancom/shared/assets/js/utils/colors';
140
140
  import { COLORS_IMAGES_TYPES } from '@lancom/shared/assets/js/constants/colors';
141
141
  import FabricHelper from '@lancom/shared/assets/js/utils/fabric-helper';
142
142
  import { findParentByPredicate } from '@lancom/shared/assets/js/utils/dom';
143
143
  import Breakpoints from '@lancom/shared/assets/js/utils/breakpoints';
144
- import { getColorBackgroundStyle } from '@lancom/shared/assets/js/utils/colors';
144
+ import { getPrintAreaByName } from '@lancom/shared/assets/js/models/print-area';
145
145
  import FileUploader from '@lancom/shared/components/common/file_uploader';
146
146
 
147
147
  export default {
@@ -158,7 +158,8 @@ export default {
158
158
  type: Object
159
159
  },
160
160
  printAreaSize: {
161
- type: String | Object
161
+ type: [String, Object],
162
+ default: null
162
163
  },
163
164
  zoomSize: {
164
165
  type: Number
@@ -178,6 +179,7 @@ export default {
178
179
  visibleWireframe: false,
179
180
  visibleOnpress: false,
180
181
  fabricHelper: null,
182
+ pendingAutoFit: false,
181
183
  redrawWithThrottle: throttle(this.redraw, 100, { trailing: false }),
182
184
  saveLayersAsImageWithDebounce: debounce(this.saveLayersAsImage, 400),
183
185
  onResize: debounce(this.handleWorkspaceSize, 1000),
@@ -204,7 +206,8 @@ export default {
204
206
  'offsetWarningVisible',
205
207
  'showRecommendationToUseLargerImage',
206
208
  'showErrorAboutSmallImage',
207
- 'previewPrintArea'
209
+ 'previewPrintArea',
210
+ 'autoFitTrigger'
208
211
  ]),
209
212
  editableLayersCount() {
210
213
  return this.editableLayers?.length;
@@ -293,6 +296,11 @@ export default {
293
296
  this.fabricHelper.setPrintArea(value, this.editorSize, this.product);
294
297
  this.redraw();
295
298
  },
299
+ autoFitTrigger() {
300
+ if (this.fabricHelper && this.selectedLayer) {
301
+ this.pendingAutoFit = true;
302
+ }
303
+ },
296
304
  zoomSize() {
297
305
  this.handleWorkspaceSize();
298
306
  }
@@ -317,15 +325,16 @@ export default {
317
325
  'setEditModeSelectedLayer',
318
326
  'setSelectedLayer',
319
327
  'removeTemplateLayer',
320
- 'setOffsetWarningVisible'
328
+ 'setOffsetWarningVisible',
329
+ 'setEditorSize'
321
330
  ]),
322
331
  ...mapMutations('layers', [
323
332
  'setLayersThumbnail'
324
333
  ]),
325
334
  editFirstLayer() {
326
335
  const layer = this.printAreaLayers.find(l => l.editableDetails);
336
+ this.setSelectedLayer(layer);
327
337
  if (layer?.editableDetails) {
328
- this.setSelectedLayer(layer);
329
338
  this.$nextTick(() => this.setEditModeSelectedLayer(true));
330
339
  }
331
340
  },
@@ -350,8 +359,11 @@ export default {
350
359
  return (!skipZoom && this.zoomSize) || this.editorSizeBreakpoints[this.breakpoints.is] || 430;
351
360
  },
352
361
  handleWorkspaceSize() {
362
+ console.log('handleWorkspaceSize...');
353
363
  const size = this.calcWorkspaceSize();
354
364
  this.fabricHelper.scaleWorkspace({ width: size, height: size });
365
+ this.setEditorSize({ width: size, height: size });
366
+ console.log('workspace', { size: this.calcWorkspaceSize(true), fabricHelper: this.fabricHelper });
355
367
  this.$emit('workspace', { size: this.calcWorkspaceSize(true), fabricHelper: this.fabricHelper });
356
368
  },
357
369
  initEventsListeners() {
@@ -380,11 +392,11 @@ export default {
380
392
  this.setSelectedLayerField(data);
381
393
  this.saveLayersAsImageWithDebounce();
382
394
  });
383
- this.fabricHelper.on('selectLayer', (layer) => {
395
+ this.fabricHelper.on('selectLayer', layer => {
384
396
  this.setSelectedLayer(layer);
385
- this.$nextTick(() => this.setEditModeSelectedLayer(true));
397
+ // this.$nextTick(() => this.setEditModeSelectedLayer(true));
386
398
  });
387
- this.fabricHelper.on('removeLayer', (layer) => {
399
+ this.fabricHelper.on('removeLayer', layer => {
388
400
  setTimeout(() => {
389
401
  this.removeTemplateLayer(layer);
390
402
  // if (!this.editModeSelectedLayer) {
@@ -395,6 +407,7 @@ export default {
395
407
  }, 100);
396
408
  });
397
409
  this.fabricHelper.on('outOfPrintArea', this.setOffsetWarningVisibility);
410
+ this.fabricHelper.on('checkPrintAreaChange', this.handlePrintAreaChange);
398
411
 
399
412
  this.updateBackgroundImage();
400
413
  this.redrawWithThrottle();
@@ -403,12 +416,19 @@ export default {
403
416
  if (this.drawingInProcess) {
404
417
  return;
405
418
  }
406
- console.log('redraw...');
407
419
  this.drawingInProcess = true;
408
420
  this.fabricHelper.clear();
409
421
  this.fabricHelper.addBoundingArea();
410
422
  await this.addLayersToCanvas();
411
423
  this.drawingInProcess = false;
424
+
425
+ if (this.pendingAutoFit) {
426
+ this.pendingAutoFit = false;
427
+ this.$nextTick(() => {
428
+ this.fabricHelper.autoFitActiveObject();
429
+ });
430
+ }
431
+
412
432
  this.saveLayersAsImageWithDebounce();
413
433
  },
414
434
  async addLayersToCanvas() {
@@ -481,6 +501,57 @@ export default {
481
501
  setOffsetWarningVisibility(visible) {
482
502
  this.setOffsetWarningVisible(visible);
483
503
  },
504
+ handlePrintAreaChange({ top, left, boundingRect }) {
505
+ if (!this.selectedLayer) {
506
+ return;
507
+ }
508
+
509
+ const printAreas = this.product.printAreas?.filter(pa => pa.side === this.side) || [];
510
+
511
+ for (const printArea of printAreas) {
512
+ const printAreaRect = getPrintAreaByName({
513
+ printArea: printArea.parentPrintArea || printArea._id,
514
+ printSize: printArea.printSize,
515
+ printAreaOffsets: printArea.printAreaOffsets,
516
+ editorWidth: this.editorSize.width,
517
+ editorHeight: this.editorSize.height
518
+ }, this.product, true);
519
+
520
+ const overlaps = boundingRect ? this.checkBoundingBoxOverlap(boundingRect, printAreaRect) : this.isPointInPrintArea(top, left, printAreaRect);
521
+
522
+ if (overlaps) {
523
+ const currentPrintAreaId = this.selectedLayer.printArea;
524
+ const newPrintAreaId = printArea.parentPrintArea || printArea._id;
525
+
526
+ if (currentPrintAreaId !== newPrintAreaId) {
527
+ this.setSelectedLayerField({ field: 'printArea', value: newPrintAreaId });
528
+ this.setSelectedLayerField({ field: 'printSize', value: printArea.printSize?._id });
529
+ this.$emit('print-area-changed', { printArea, size: printArea.printSize });
530
+ }
531
+ break;
532
+ }
533
+ }
534
+ },
535
+ checkBoundingBoxOverlap(layerBoundingRect, printAreaRect) {
536
+ const layerRight = layerBoundingRect.left + layerBoundingRect.width;
537
+ const layerBottom = layerBoundingRect.top + layerBoundingRect.height;
538
+ const printAreaRight = printAreaRect.left + printAreaRect.width;
539
+ const printAreaBottom = printAreaRect.top + printAreaRect.height;
540
+
541
+ const overlapsHorizontally = layerBoundingRect.left < printAreaRight && layerRight > printAreaRect.left;
542
+ const overlapsVertically = layerBoundingRect.top < printAreaBottom && layerBottom > printAreaRect.top;
543
+
544
+ return overlapsHorizontally && overlapsVertically;
545
+ },
546
+ isPointInPrintArea(pointTop, pointLeft, printAreaRect) {
547
+ const { top, left, width, height } = printAreaRect;
548
+ return (
549
+ pointLeft >= left &&
550
+ pointLeft <= left + width &&
551
+ pointTop >= top &&
552
+ pointTop <= top + height
553
+ );
554
+ },
484
555
  toogleBoundBox(state, option) {
485
556
  if (!this.fabricHelper || ['mini', 'xs'].includes(this.breakpoints.is)) {
486
557
  return;
@@ -1,5 +1,6 @@
1
1
  <template>
2
2
  <section
3
+ v-if="products.length > 0"
3
4
  class="ClearanceProducts__wrapper">
4
5
  <div class="ClearanceProducts__head">
5
6
  <h2 class="ClearanceProducts__title">
@@ -0,0 +1,57 @@
1
+ .ProductDocuments {
2
+ &__wrapper {
3
+ margin-bottom: 20px;
4
+ }
5
+
6
+ &__header {
7
+ margin-bottom: 16px;
8
+ }
9
+
10
+ &__content {
11
+ width: 100%;
12
+ }
13
+
14
+ &__table {
15
+ width: 100%;
16
+ }
17
+
18
+ &__icon-container {
19
+ width: 80px;
20
+ text-align: center;
21
+ padding: 16px;
22
+ }
23
+
24
+ &__icon {
25
+ width: 48px;
26
+ height: 48px;
27
+ object-fit: contain;
28
+ }
29
+
30
+ &__list {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: 8px;
34
+ }
35
+
36
+ &__item {
37
+ display: flex;
38
+ align-items: center;
39
+ margin-left: 20px;
40
+ }
41
+
42
+ &__link {
43
+ text-decoration: none;
44
+ color: inherit;
45
+ transition: color 0.2s ease;
46
+
47
+ &:hover {
48
+ color: #0066cc;
49
+ text-decoration: underline;
50
+ }
51
+ }
52
+
53
+ &__empty {
54
+ text-align: center;
55
+ padding: 24px;
56
+ }
57
+ }
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <div class="ProductDocuments__wrapper">
3
+ <div class="ProductDocuments__header">
4
+ <h5 class="lc_semibold20 lc_black">
5
+ Documents
6
+ </h5>
7
+ </div>
8
+ <div class="ProductDocuments__content">
9
+ <table class="lc_table bordered ProductDocuments__table">
10
+ <tbody class="centered">
11
+ <tr
12
+ v-for="(document, index) in product.documents"
13
+ :key="document._id">
14
+ <td>
15
+ <div class="ProductDocuments__item">
16
+ <a
17
+ :href="document.url"
18
+ target="_blank"
19
+ class="lc_medium16 lc_black ProductDocuments__link">
20
+ {{ document.name || `Document ${index + 1}` }}
21
+ </a>
22
+ </div>
23
+ </td>
24
+ </tr>
25
+ </tbody>
26
+ </table>
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <script>
32
+
33
+ export default {
34
+ name: 'ProductDocuments',
35
+ props: {
36
+ product: {
37
+ type: Object,
38
+ required: true
39
+ }
40
+ }
41
+ };
42
+ </script>
43
+
44
+ <style lang="scss" scoped>
45
+ @import 'product-documents';
46
+ </style>
@@ -0,0 +1,62 @@
1
+ @import '@lancom/shared/assets/scss/variables';
2
+
3
+ .ProductImages__wrapper {
4
+ display: grid;
5
+ grid-template-columns: 1fr 1fr;
6
+ gap: 16px;
7
+ width: 100%;
8
+ margin: 24px 0;
9
+
10
+ @media (max-width: $bp-small-max) {
11
+ gap: 12px;
12
+ margin: 16px 0;
13
+ }
14
+
15
+ @media (max-width: $bp-extra-small-max) {
16
+ gap: 8px;
17
+ margin: 12px 0;
18
+ }
19
+ }
20
+
21
+ .ProductImages__item {
22
+ position: relative;
23
+ overflow: hidden;
24
+ border-radius: 8px;
25
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
26
+
27
+ &:hover {
28
+ transform: translateY(-2px);
29
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
30
+ }
31
+
32
+ &--full {
33
+ grid-column: 1 / -1;
34
+ aspect-ratio: 1 / 1;
35
+ }
36
+
37
+ &--half {
38
+ grid-column: span 1;
39
+ aspect-ratio: 1 / 1;
40
+ }
41
+
42
+ @media (max-width: $bp-small-max) {
43
+ border-radius: 4px;
44
+
45
+ &:hover {
46
+ transform: none;
47
+ box-shadow: none;
48
+ }
49
+ }
50
+ }
51
+
52
+ .ProductImages__image {
53
+ width: 100%;
54
+ height: 100%;
55
+ object-fit: cover;
56
+ display: block;
57
+ transition: opacity 0.3s ease;
58
+
59
+ &:hover {
60
+ opacity: 0.95;
61
+ }
62
+ }
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <div v-if="hasImages" class="ProductImages__wrapper">
3
+ <div
4
+ v-for="(image, index) in modelImages"
5
+ :key="`model-${index}`"
6
+ class="ProductImages__item ProductImages__item--full">
7
+ <img
8
+ :src="getImageUrl(image)"
9
+ :alt="image.alt"
10
+ class="ProductImages__image"
11
+ loading="lazy" />
12
+ </div>
13
+ <div
14
+ v-for="(image, index) in additionalImages"
15
+ :key="`additional-${index}`"
16
+ class="ProductImages__item ProductImages__item--half">
17
+ <img
18
+ :src="getImageUrl(image)"
19
+ :alt="image.alt"
20
+ class="ProductImages__image"
21
+ loading="lazy" />
22
+ </div>
23
+ </div>
24
+ </template>
25
+
26
+ <script>
27
+ import { mapGetters } from 'vuex';
28
+ import { staticLink } from '@lancom/shared/assets/js/utils/filters';
29
+ import { isValidImageType } from '@lancom/shared/assets/js/utils/colors';
30
+
31
+ export default {
32
+ name: 'ProductImages',
33
+ computed: {
34
+ ...mapGetters('product', [
35
+ 'product',
36
+ 'images',
37
+ 'editableColor'
38
+ ]),
39
+ colorImages() {
40
+ if (!this.editableColor) {
41
+ return this.images;
42
+ }
43
+ return this.images.filter(img => img.color === this.editableColor._id);
44
+ },
45
+ modelImages() {
46
+ return this.colorImages.filter(img => isValidImageType(img, 'model'));
47
+ },
48
+ additionalImages() {
49
+ return this.colorImages.filter(img => isValidImageType(img, 'additional_image_link'));
50
+ },
51
+ hasImages() {
52
+ return this.modelImages.length > 0 || this.additionalImages.length > 0;
53
+ }
54
+ },
55
+ methods: {
56
+ getImageUrl(image) {
57
+ return staticLink(image.large || image.medium || image.small);
58
+ }
59
+ }
60
+ };
61
+ </script>
62
+
63
+ <style lang="scss" scoped>
64
+ @import 'product-images';
65
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.461",
3
+ "version": "0.0.463",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {
package/store/product.js CHANGED
@@ -32,7 +32,8 @@ export const state = () => ({
32
32
  sm: 580,
33
33
  md: 430,
34
34
  lg: 600,
35
- xl: 720
35
+ xl: 720,
36
+ xxl: 720
36
37
  },
37
38
  template: {
38
39
  visibleSteps: false,
@@ -61,6 +62,7 @@ export const state = () => ({
61
62
  selectedTab: null,
62
63
  previewPrintArea: null,
63
64
  layersUpdatesCount: 0,
65
+ autoFitTrigger: 0,
64
66
  editableSide: { id: 'front' }
65
67
  });
66
68
 
@@ -89,6 +91,7 @@ export const getters = {
89
91
  usedSimpleProducts: ({ template }) => (template.simpleProducts || []).filter(p => p.amount > 0),
90
92
  usedSimpleProductsQuantity: ({ template }) => (template.simpleProducts || []).filter(p => p.amount > 0).reduce((sum, { amount }) => sum + amount, 0),
91
93
  usedBigSizeSimpleProductsQuantity: (state, { usedSimpleProducts }) => filterBigSize(usedSimpleProducts).reduce((sum, { amount }) => sum + amount, 0),
94
+ autoFitTrigger: ({ autoFitTrigger }) => autoFitTrigger,
92
95
  defaultSimpleProduct: ({ productDetails, editableColor, defaultSize }) => {
93
96
  let simpleProduct = null;
94
97
  if (defaultSize) {
@@ -545,6 +548,9 @@ export const mutations = {
545
548
  increaseLayersUpdatesCount(state) {
546
549
  state.layersUpdatesCount++;
547
550
  },
551
+ triggerAutoFit(state) {
552
+ state.autoFitTrigger++;
553
+ },
548
554
  /*
549
555
  ** MANAGE COLORS
550
556
  */