¨4.0.1¨
This commit is contained in:
153
Modules/Product/Resources/assets/admin/js/create.js
Normal file
153
Modules/Product/Resources/assets/admin/js/create.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import Vue from "vue";
|
||||
import ProductMixin from "./mixins/ProductMixin";
|
||||
import Errors from "@admin/js/Errors";
|
||||
import { generateSlug } from "@admin/js/functions";
|
||||
|
||||
Vue.prototype.route = route;
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
|
||||
mixins: [ProductMixin],
|
||||
|
||||
data: {
|
||||
formSubmissionType: null,
|
||||
form: {
|
||||
brand_id: "",
|
||||
tax_class_id: "",
|
||||
is_active: true,
|
||||
media: [],
|
||||
is_virtual: false,
|
||||
manage_stock: 0,
|
||||
in_stock: 1,
|
||||
special_price_type: "fixed",
|
||||
meta: {},
|
||||
attributes: [],
|
||||
downloads: [],
|
||||
variations: [],
|
||||
variants: [],
|
||||
options: [],
|
||||
slug: null,
|
||||
},
|
||||
errors: new Errors(),
|
||||
selectizeConfig: {
|
||||
plugins: ["remove_button"],
|
||||
},
|
||||
searchableSelectizeConfig: {},
|
||||
categoriesSelectizeConfig: {
|
||||
plugins: ["remove_button"],
|
||||
onItemAdd(value) {
|
||||
this.getItem(value)[0].innerHTML = this.getItem(
|
||||
value
|
||||
)[0].innerHTML.replace(/¦––\s/g, "");
|
||||
},
|
||||
},
|
||||
flatPickrConfig: {
|
||||
mode: "single",
|
||||
enableTime: true,
|
||||
altInput: true,
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.setSearchableSelectizeConfig();
|
||||
},
|
||||
|
||||
methods: {
|
||||
setProductSlug(value) {
|
||||
this.form.slug = generateSlug(value);
|
||||
},
|
||||
|
||||
setFormDefaultData() {
|
||||
this.form = {
|
||||
brand_id: "",
|
||||
tax_class_id: "",
|
||||
is_active: true,
|
||||
media: [],
|
||||
is_virtual: false,
|
||||
manage_stock: 0,
|
||||
in_stock: 1,
|
||||
special_price_type: "fixed",
|
||||
meta: {},
|
||||
attributes: [],
|
||||
downloads: [],
|
||||
variations: [],
|
||||
variants: [],
|
||||
options: [],
|
||||
slug: null,
|
||||
};
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
this.setFormDefaultData();
|
||||
tinyMCE.get("description").setContent("");
|
||||
this.resetBulkEditVariantFields();
|
||||
|
||||
this.focusField({
|
||||
selector: "#name",
|
||||
});
|
||||
},
|
||||
|
||||
submit({ submissionType }) {
|
||||
this.formSubmissionType = submissionType;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: route("admin.products.store", {
|
||||
...((submissionType === "save_and_edit" ||
|
||||
submissionType === "save_and_exit") && {
|
||||
exit_flash: true,
|
||||
}),
|
||||
}),
|
||||
data: this.transformData(this.form),
|
||||
dataType: "json",
|
||||
success: ({ message, product_id }) => {
|
||||
if (submissionType === "save_and_edit") {
|
||||
location.href = route(
|
||||
"admin.products.edit",
|
||||
product_id
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (submissionType === "save_and_exit") {
|
||||
location.href = route("admin.products.index");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toaster(message, {
|
||||
type: "success",
|
||||
});
|
||||
|
||||
this.resetForm();
|
||||
},
|
||||
})
|
||||
.catch((error) => {
|
||||
this.formSubmissionType = null;
|
||||
|
||||
toaster(error.responseJSON.message, {
|
||||
type: "default",
|
||||
});
|
||||
|
||||
if (error.status === 422) {
|
||||
this.errors.reset();
|
||||
this.errors.record(error.responseJSON.errors);
|
||||
this.scrollToFirstErrorField(this.$refs.form.elements);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hasAnyVariant) {
|
||||
this.regenerateVariationsAndVariantsUid();
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
if (submissionType === "save") {
|
||||
this.formSubmissionType = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
145
Modules/Product/Resources/assets/admin/js/edit.js
Normal file
145
Modules/Product/Resources/assets/admin/js/edit.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import Vue from "vue";
|
||||
import ProductMixin from "./mixins/ProductMixin";
|
||||
import Errors from "@admin/js/Errors";
|
||||
|
||||
Vue.prototype.route = route;
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
|
||||
mixins: [ProductMixin],
|
||||
|
||||
data: {
|
||||
formSubmissionType: null,
|
||||
form: {
|
||||
brand_id: "",
|
||||
categories: [],
|
||||
tags: [],
|
||||
media: [],
|
||||
name: "lorem ipsum",
|
||||
description: "<p>lorem ipsum dolor</p>",
|
||||
is_active: 1,
|
||||
price: 999,
|
||||
tax_class_id: "",
|
||||
is_virtual: 0,
|
||||
manage_stock: 0,
|
||||
in_stock: 1,
|
||||
special_price_type: "fixed",
|
||||
meta: {},
|
||||
variations: [],
|
||||
variants: [],
|
||||
attributes: [],
|
||||
options: [],
|
||||
downloads: [],
|
||||
},
|
||||
errors: new Errors(),
|
||||
selectizeConfig: {
|
||||
plugins: ["remove_button"],
|
||||
},
|
||||
searchableSelectizeConfig: {},
|
||||
categoriesSelectizeConfig: {
|
||||
plugins: ["remove_button"],
|
||||
onItemAdd(value) {
|
||||
this.getItem(value)[0].innerHTML = this.getItem(
|
||||
value
|
||||
)[0].innerHTML.replace(/¦––\s/g, "");
|
||||
},
|
||||
},
|
||||
flatPickrConfig: {
|
||||
mode: "single",
|
||||
enableTime: true,
|
||||
altInput: true,
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.setFormData();
|
||||
this.setSearchableSelectizeConfig();
|
||||
this.setDefaultVariantUid();
|
||||
this.setVariantsLength();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareFormData(formData) {
|
||||
this.prepareAttributes(formData.attributes);
|
||||
this.prepareVariations(formData.variations);
|
||||
this.prepareVariants(formData.variants);
|
||||
this.prepareOptions(formData.options);
|
||||
|
||||
return formData;
|
||||
},
|
||||
|
||||
setFormData() {
|
||||
this.form = { ...this.prepareFormData(FleetCart.data["product"]) };
|
||||
},
|
||||
|
||||
setDefaultVariantUid() {
|
||||
if (this.hasAnyVariant) {
|
||||
this.defaultVariantUid = this.form.variants.find(
|
||||
({ is_default }) => is_default === true
|
||||
).uid;
|
||||
}
|
||||
},
|
||||
|
||||
setVariantsLength() {
|
||||
this.variantsLength = this.form.variants.length;
|
||||
},
|
||||
|
||||
submit({ submissionType }) {
|
||||
this.formSubmissionType = submissionType;
|
||||
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
url: route("admin.products.update", {
|
||||
id: this.form.id,
|
||||
...(submissionType === "save_and_exit" && {
|
||||
exit_flash: true,
|
||||
}),
|
||||
}),
|
||||
data: this.transformData(this.form),
|
||||
dataType: "json",
|
||||
success: (response) => {
|
||||
if (submissionType === "save_and_exit") {
|
||||
location.href = route("admin.products.index");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.form = { ...response.product_resource };
|
||||
|
||||
this.errors.reset();
|
||||
this.prepareFormData(this.form);
|
||||
this.resetBulkEditVariantFields();
|
||||
|
||||
toaster(response.message, {
|
||||
type: "success",
|
||||
});
|
||||
},
|
||||
})
|
||||
.catch((error) => {
|
||||
this.formSubmissionType = null;
|
||||
|
||||
toaster(error.responseJSON.message, {
|
||||
type: "default",
|
||||
});
|
||||
|
||||
if (error.status === 422) {
|
||||
this.errors.reset();
|
||||
this.errors.record(error.responseJSON.errors);
|
||||
this.scrollToFirstErrorField(this.$refs.form.elements);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hasAnyVariant) {
|
||||
this.regenerateVariationsAndVariantsUid();
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
if (submissionType === "save") {
|
||||
this.formSubmissionType = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
export default {
|
||||
watch: {
|
||||
"form.attributes": {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (newValue.length === 0) {
|
||||
this.addAttribute();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareAttributes(attributes) {
|
||||
attributes.forEach((attribute) => {
|
||||
this.$set(attribute, "uid", this.uid());
|
||||
});
|
||||
},
|
||||
|
||||
getAttributeValuesById(id) {
|
||||
let values = null;
|
||||
let matched = false;
|
||||
|
||||
if (id === "") return;
|
||||
|
||||
Object.values(FleetCart.data["attribute-sets"]).some(
|
||||
(attributeSet) => {
|
||||
attributeSet.attributes.some((attribute) => {
|
||||
if (attribute.id === id) {
|
||||
matched = true;
|
||||
values = attribute.values;
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (matched) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
focusAttributeValueField(index) {
|
||||
this.$refs.attributeValues[index].$el.selectize.focus();
|
||||
},
|
||||
|
||||
addAttribute() {
|
||||
this.form.attributes.push({
|
||||
attribute_id: "",
|
||||
uid: this.uid(),
|
||||
values: [],
|
||||
});
|
||||
},
|
||||
|
||||
deleteAttribute(index, uid) {
|
||||
this.form.attributes.splice(index, 1);
|
||||
this.clearErrors({ name: "attributes", uid });
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,224 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
bulkEditVariantsUid: "",
|
||||
bulkEditVariantsField: "",
|
||||
bulkEditVariants: {},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasBulkEditVariantsUid() {
|
||||
return this.bulkEditVariantsUid !== "";
|
||||
},
|
||||
|
||||
hasBulkEditVariantsField() {
|
||||
return this.bulkEditVariantsField !== "";
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeBulkEditVariantsUid(uid) {
|
||||
this.resetVariantsSelection();
|
||||
|
||||
if (uid === "") {
|
||||
this.resetBulkEditVariantsField();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectVariants(uid);
|
||||
},
|
||||
|
||||
selectVariants(uid) {
|
||||
this.resetVariantsSelection();
|
||||
|
||||
if (uid === "") return;
|
||||
|
||||
if (uid !== "all") {
|
||||
this.selectSpecificVariants(uid);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectAllVariants();
|
||||
},
|
||||
|
||||
selectAllVariants() {
|
||||
this.form.variants.forEach((variant) => {
|
||||
this.$set(variant, "is_selected", true);
|
||||
});
|
||||
},
|
||||
|
||||
selectSpecificVariants(uid) {
|
||||
this.form.variants.forEach((variant) => {
|
||||
if (variant.uids.includes(uid)) {
|
||||
this.$set(variant, "is_selected", true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
changeBulkEditVariantsField(fieldName) {
|
||||
const focusableFieldNames = ["sku", "price", "special_price"];
|
||||
|
||||
if (focusableFieldNames.includes(fieldName)) {
|
||||
this.focusField({
|
||||
selector: `#bulk-edit-variants-${fieldName.replace(
|
||||
/_/g,
|
||||
"-"
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
|
||||
this.resetBulkEditVariants();
|
||||
},
|
||||
|
||||
addBulkEditVariantsMedia() {
|
||||
const picker = new MediaPicker({ type: "image", multiple: true });
|
||||
|
||||
picker.on("select", ({ id, path }) => {
|
||||
this.bulkEditVariants.media.push({
|
||||
id: +id,
|
||||
path,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeBulkEditVariantsMedia(index) {
|
||||
this.bulkEditVariants.media.splice(index, 1);
|
||||
},
|
||||
|
||||
bulkEditvariantsDefaultData() {
|
||||
return {
|
||||
is_active: true,
|
||||
media: [],
|
||||
price: null,
|
||||
special_price: null,
|
||||
special_price_type: "fixed",
|
||||
special_price_start: null,
|
||||
special_price_end: null,
|
||||
manage_stock: 0,
|
||||
qty: null,
|
||||
in_stock: 1,
|
||||
};
|
||||
},
|
||||
|
||||
resetBulkEditVariantFields() {
|
||||
this.bulkEditVariantsUid = "";
|
||||
|
||||
this.resetVariantsSelection();
|
||||
this.resetBulkEditVariantsField();
|
||||
},
|
||||
|
||||
resetBulkEditVariantsField() {
|
||||
this.bulkEditVariantsField = "";
|
||||
|
||||
this.resetBulkEditVariants();
|
||||
},
|
||||
|
||||
resetBulkEditVariants() {
|
||||
this.bulkEditVariants = {
|
||||
...this.bulkEditvariantsDefaultData(),
|
||||
};
|
||||
},
|
||||
|
||||
resetVariantsSelection() {
|
||||
this.form.variants.forEach((variant) => {
|
||||
this.$set(variant, "is_selected", false);
|
||||
});
|
||||
},
|
||||
|
||||
clearVariantsSpecialPriceErrors(uid) {
|
||||
Object.keys(this.errors).forEach((key) => {
|
||||
if (
|
||||
key.startsWith(`variants.${uid}`) &&
|
||||
key.includes("special_price")
|
||||
) {
|
||||
this.errors.clear(key);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateVariantsField(variant, { key, value }) {
|
||||
this.$set(variant, key, value);
|
||||
|
||||
this.errors.clear(`variants.${variant.uid}.${key}`);
|
||||
},
|
||||
|
||||
updateVariantsStatus(variant, { key, value }) {
|
||||
if (variant.is_default === true) return;
|
||||
|
||||
this.$set(variant, key, value);
|
||||
|
||||
this.errors.clear(`variants.${variant.uid}.${key}`);
|
||||
},
|
||||
|
||||
updateVariantsSpecialPrice(
|
||||
variant,
|
||||
{ key, value },
|
||||
{ special_price_type, special_price_start, special_price_end }
|
||||
) {
|
||||
this.$set(variant, key, value);
|
||||
this.$set(variant, "special_price_type", special_price_type);
|
||||
this.$set(variant, "special_price_start", special_price_start);
|
||||
this.$set(variant, "special_price_end", special_price_end);
|
||||
|
||||
this.clearVariantsSpecialPriceErrors(variant.uid);
|
||||
},
|
||||
|
||||
updateVariantsManageStock(variant, { key, value }, { qty }) {
|
||||
this.$set(variant, key, value);
|
||||
this.$set(variant, "qty", qty);
|
||||
|
||||
this.errors.clear([
|
||||
`variants.${variant.uid}.${key}`,
|
||||
`variants.${variant.uid}.qty`,
|
||||
]);
|
||||
},
|
||||
|
||||
callUpdateVariantsMethodByField(key) {
|
||||
return {
|
||||
media: this.updateVariantsField,
|
||||
sku: this.updateVariantsField,
|
||||
is_active: this.updateVariantsStatus,
|
||||
price: this.updateVariantsField,
|
||||
special_price: this.updateVariantsSpecialPrice,
|
||||
manage_stock: this.updateVariantsManageStock,
|
||||
in_stock: this.updateVariantsField,
|
||||
}[key];
|
||||
},
|
||||
|
||||
updateVariants(field) {
|
||||
this.form.variants.forEach((variant) => {
|
||||
if (variant.is_selected) {
|
||||
this.callUpdateVariantsMethodByField(field.key)(
|
||||
variant,
|
||||
field,
|
||||
this.bulkEditVariants
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
bulkUpdateVariants() {
|
||||
if (
|
||||
!this.hasBulkEditVariantsUid &&
|
||||
!this.hasBulkEditVariantsField
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = {
|
||||
key: this.bulkEditVariantsField,
|
||||
value: this.bulkEditVariants[this.bulkEditVariantsField],
|
||||
};
|
||||
|
||||
this.updateVariants(field);
|
||||
this.resetBulkEditVariantFields();
|
||||
|
||||
toaster(trans("product::products.variants.bulk_variants_updated"), {
|
||||
type: "default",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
export default {
|
||||
methods: {
|
||||
transformMedia(formData) {
|
||||
formData.media = formData.media.map((data) => data.id);
|
||||
},
|
||||
|
||||
transformAttributes(formData) {
|
||||
formData.attributes = formData.attributes
|
||||
.filter(({ attribute_id }) => attribute_id !== "")
|
||||
.reduce((accumulator, { attribute_id, uid, values }) => {
|
||||
return { ...accumulator, [uid]: { attribute_id, values } };
|
||||
}, {});
|
||||
},
|
||||
|
||||
transformDownloads(formData) {
|
||||
formData.downloads = formData.downloads
|
||||
.filter(({ id }) => id !== null)
|
||||
.map(({ id }) => id);
|
||||
},
|
||||
|
||||
transformVariations(formData) {
|
||||
const PATHS = {
|
||||
text: ["id", "uid", "label"],
|
||||
color: ["id", "uid", "label", "color"],
|
||||
image: ["id", "uid", "label", "image"],
|
||||
};
|
||||
|
||||
formData.variations = formData.variations
|
||||
.filter(({ name, type }) => Boolean(name) || type !== "")
|
||||
.reduce((accumulator, variation) => {
|
||||
if (variation.type === "") {
|
||||
variation.values = [];
|
||||
} else {
|
||||
variation.values = variation.values.reduce(
|
||||
(valueAccumulator, value) => {
|
||||
value = _.pick(value, PATHS[variation.type]);
|
||||
|
||||
if (variation.type === "image") {
|
||||
value.image = value.image.id;
|
||||
}
|
||||
|
||||
return {
|
||||
...valueAccumulator,
|
||||
[value.uid]: value,
|
||||
};
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
return { ...accumulator, [variation.uid]: variation };
|
||||
}, {});
|
||||
},
|
||||
|
||||
transformVariants(formData) {
|
||||
formData.variants = formData.variants.reduce(
|
||||
(accumulator, variant) => {
|
||||
variant.media = variant.media.map(({ id }) => id);
|
||||
|
||||
return { ...accumulator, [variant.uid]: variant };
|
||||
},
|
||||
{}
|
||||
);
|
||||
},
|
||||
|
||||
transformOptions(formData) {
|
||||
const PATHS = {
|
||||
text: ["id", "uid", "price", "price_type"],
|
||||
select: ["id", "uid", "label", "price", "price_type"],
|
||||
};
|
||||
|
||||
formData.options = formData.options
|
||||
.filter(({ name, type }) => Boolean(name) || type !== "")
|
||||
.reduce((accumulator, option) => {
|
||||
if (option.type === "") {
|
||||
option.values = [];
|
||||
} else {
|
||||
option.values = option.values.reduce(
|
||||
(valueAccumulator, value) => {
|
||||
const optionType = this.isOptionTypeText(option)
|
||||
? "text"
|
||||
: "select";
|
||||
|
||||
value = _.pick(value, PATHS[optionType]);
|
||||
|
||||
return {
|
||||
...valueAccumulator,
|
||||
[value.uid]: value,
|
||||
};
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
return { ...accumulator, [option.uid]: option };
|
||||
}, {});
|
||||
},
|
||||
|
||||
transformData(data) {
|
||||
const formData = JSON.parse(JSON.stringify(data));
|
||||
|
||||
this.transformMedia(formData);
|
||||
this.transformAttributes(formData);
|
||||
this.transformDownloads(formData);
|
||||
this.transformVariations(formData);
|
||||
this.transformVariants(formData);
|
||||
this.transformOptions(formData);
|
||||
|
||||
return formData;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
export default {
|
||||
watch: {
|
||||
"form.downloads": {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (newValue.length === 0) {
|
||||
this.addDownload({ preventMediaPicker: true });
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
addDownload({ preventMediaPicker }) {
|
||||
const downloads = this.form.downloads;
|
||||
|
||||
downloads.push({
|
||||
id: null,
|
||||
filename: null,
|
||||
});
|
||||
|
||||
if (!preventMediaPicker) {
|
||||
this.chooseDownloadableFile(downloads.length - 1);
|
||||
}
|
||||
},
|
||||
|
||||
deleteDownload(index) {
|
||||
this.form.downloads.splice(index, 1);
|
||||
},
|
||||
|
||||
chooseDownloadableFile(index) {
|
||||
let picker = new MediaPicker();
|
||||
|
||||
picker.on("select", ({ id, filename }) => {
|
||||
this.form.downloads.splice(index, 1, {
|
||||
id,
|
||||
filename,
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLeftColumnSectionDragging: false,
|
||||
isRightColumnSectionDragging: false,
|
||||
formLeftSections: [],
|
||||
formRightSections: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
storeFormSections() {
|
||||
return {
|
||||
get: (sortable) => {
|
||||
return this.getSectionOrder(sortable.options.dataName);
|
||||
},
|
||||
set: (sortable) => {
|
||||
return this.setSectionOrder(sortable);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.formLeftSections = this.getSectionOrder(
|
||||
"product-form-left-sections"
|
||||
);
|
||||
this.formRightSections = this.getSectionOrder(
|
||||
"product-form-right-sections"
|
||||
);
|
||||
},
|
||||
|
||||
methods: {
|
||||
getDefaultSectionOrder(key) {
|
||||
return {
|
||||
"product-form-left-sections": [
|
||||
"attributes",
|
||||
"downloads",
|
||||
"variations",
|
||||
"variants",
|
||||
"options",
|
||||
],
|
||||
"product-form-right-sections": [
|
||||
"price",
|
||||
"inventory",
|
||||
"media",
|
||||
"linked_products",
|
||||
"seo",
|
||||
"additional",
|
||||
],
|
||||
}[key];
|
||||
},
|
||||
|
||||
getSectionOrder(key) {
|
||||
const sectionsOrder = JSON.parse(localStorage.getItem(key));
|
||||
|
||||
return sectionsOrder === null
|
||||
? this.getDefaultSectionOrder(key)
|
||||
: sectionsOrder;
|
||||
},
|
||||
|
||||
setSectionOrder(sortable) {
|
||||
this.$nextTick(() => {
|
||||
localStorage.setItem(
|
||||
sortable.options.dataName,
|
||||
JSON.stringify(sortable.toArray())
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
notifySectionOrderChange() {
|
||||
toaster(trans("product::products.section.order_saved"), {
|
||||
type: "default",
|
||||
});
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.initColorPicker();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
198
Modules/Product/Resources/assets/admin/js/mixins/OptionMixin.js
Normal file
198
Modules/Product/Resources/assets/admin/js/mixins/OptionMixin.js
Normal file
@@ -0,0 +1,198 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
globalOptionId: "",
|
||||
addingGlobalOption: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isAddGlobalOptionDisabled() {
|
||||
return this.globalOptionId === "" || this.addingGlobalOption;
|
||||
},
|
||||
|
||||
isCollapsedOptionsAccordion() {
|
||||
return this.form.options.every(({ is_open }) => is_open === false);
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
"form.options": {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (newValue.length === 0) {
|
||||
this.addOption({ preventFocus: true });
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareOptions(options) {
|
||||
options.forEach((option) => {
|
||||
this.$set(option, "uid", this.uid());
|
||||
this.$set(option, "is_open", false);
|
||||
|
||||
option.values.forEach((_, valueIndex) => {
|
||||
this.$set(option.values[valueIndex], "uid", this.uid());
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
addOption({ preventFocus }) {
|
||||
const options = this.form.options;
|
||||
const uid = this.uid();
|
||||
|
||||
options.push({
|
||||
uid,
|
||||
type: "",
|
||||
is_open: true,
|
||||
is_global: false,
|
||||
is_required: false,
|
||||
values: [
|
||||
{
|
||||
uid: this.uid(),
|
||||
price_type: "fixed",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!preventFocus) {
|
||||
this.focusField({
|
||||
selector: `#options-${uid}-name`,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
deleteOption(index, uid) {
|
||||
this.form.options.splice(index, 1);
|
||||
this.clearErrors({ name: "options", uid });
|
||||
},
|
||||
|
||||
changeOptionType(index, uid) {
|
||||
let option = this.form.options[index];
|
||||
const fieldName = this.isOptionTypeText(option) ? "price" : "label";
|
||||
|
||||
this.clearValuesError({ name: "options", uid });
|
||||
|
||||
if (option.values.length === 1) {
|
||||
this.focusField({
|
||||
selector: `#options-${uid}-values-${option.values[0].uid}-${fieldName}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
isOptionTypeText(option) {
|
||||
const TYPES = ["field", "textarea", "date", "date_time", "time"];
|
||||
|
||||
if (TYPES.includes(option.type)) {
|
||||
option.values.length > 1 && option.values.splice(1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
isOptionTypeSelect({ type }) {
|
||||
const TYPES = [
|
||||
"dropdown",
|
||||
"checkbox",
|
||||
"checkbox_custom",
|
||||
"radio",
|
||||
"radio_custom",
|
||||
"multiple_select",
|
||||
];
|
||||
|
||||
return TYPES.includes(type) ? true : false;
|
||||
},
|
||||
|
||||
addOptionRow(index, optionUid) {
|
||||
const valueUid = this.uid();
|
||||
|
||||
this.form.options[index].values.push({
|
||||
uid: valueUid,
|
||||
price_type: "fixed",
|
||||
});
|
||||
|
||||
this.focusField({
|
||||
selector: `#options-${optionUid}-values-${valueUid}-label`,
|
||||
});
|
||||
},
|
||||
|
||||
addOptionRowOnPressEnter(event, optionIndex, valueIndex) {
|
||||
const option = this.form.options[optionIndex];
|
||||
const values = option.values;
|
||||
|
||||
if (event.target.value === "") return;
|
||||
|
||||
if (values.length - 1 === valueIndex) {
|
||||
this.addOptionRow(optionIndex, option.uid);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueIndex < values.length - 1) {
|
||||
$(
|
||||
`#options-${option.uid}-values-${
|
||||
values[valueIndex + 1].uid
|
||||
}-label`
|
||||
).trigger("focus");
|
||||
}
|
||||
},
|
||||
|
||||
deleteOptionRow(optionIndex, optionUid, valueIndex, valueUid) {
|
||||
const option = this.form.options[optionIndex];
|
||||
|
||||
option.values.splice(valueIndex, 1);
|
||||
|
||||
this.clearValueRowErrors({ name: "options", optionUid, valueUid });
|
||||
|
||||
if (option.values.length === 0) {
|
||||
this.addOptionRow(optionIndex, optionUid);
|
||||
}
|
||||
},
|
||||
|
||||
addGlobalOption() {
|
||||
if (this.globalOptionId === "") return;
|
||||
|
||||
this.addingGlobalOption = true;
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: route("admin.options.show", this.globalOptionId),
|
||||
dataType: "json",
|
||||
success: (option) => {
|
||||
option.uid = this.uid();
|
||||
option.is_open = true;
|
||||
|
||||
option.values.forEach((value) => {
|
||||
value.uid = this.uid();
|
||||
});
|
||||
|
||||
this.form.options.push(option);
|
||||
|
||||
this.$nextTick(() => {
|
||||
$(`#options-${option.uid}-name`).trigger("focus");
|
||||
});
|
||||
|
||||
toaster(
|
||||
trans("product::products.options.option_inserted"),
|
||||
{
|
||||
type: "default",
|
||||
}
|
||||
);
|
||||
},
|
||||
})
|
||||
.catch((error) => {
|
||||
toaster(error.responseJSON.message, {
|
||||
type: "error",
|
||||
});
|
||||
})
|
||||
.always(() => {
|
||||
this.globalOptionId = "";
|
||||
this.addingGlobalOption = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
243
Modules/Product/Resources/assets/admin/js/mixins/ProductMixin.js
Normal file
243
Modules/Product/Resources/assets/admin/js/mixins/ProductMixin.js
Normal file
@@ -0,0 +1,243 @@
|
||||
import Admin from "@admin/js/Admin";
|
||||
import md5 from "blueimp-md5";
|
||||
import tinyMCE from "tinymce";
|
||||
import draggable from "vuedraggable";
|
||||
import Selectize from "vue2-selectize";
|
||||
import flatPickr from "vue-flatpickr-component";
|
||||
import FormSectionMixin from "./FormSectionMixin";
|
||||
import AttributeMixin from "./AttributeMixin";
|
||||
import VariationMixin from "./VariationMixin";
|
||||
import VariantMixin from "./VariantMixin";
|
||||
import BulkEditVariantsMixin from "./BulkEditVariantsMixin";
|
||||
import OptionMixin from "./OptionMixin";
|
||||
import DownloadMixin from "./DownloadMixin";
|
||||
import DataTransformMixin from "./DataTransformMixin";
|
||||
import { toaster } from "@admin/js/Toaster";
|
||||
|
||||
window.md5 = md5;
|
||||
window.toaster = toaster;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
Selectize,
|
||||
flatPickr,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
FormSectionMixin,
|
||||
AttributeMixin,
|
||||
VariationMixin,
|
||||
VariantMixin,
|
||||
BulkEditVariantsMixin,
|
||||
OptionMixin,
|
||||
DownloadMixin,
|
||||
DataTransformMixin,
|
||||
],
|
||||
|
||||
mounted() {
|
||||
this.initTinyMCE();
|
||||
|
||||
window.admin = new Admin();
|
||||
},
|
||||
|
||||
methods: {
|
||||
uid() {
|
||||
return Math.random().toString(36).slice(3);
|
||||
},
|
||||
|
||||
focusEditor() {
|
||||
tinyMCE.get("description").focus();
|
||||
},
|
||||
|
||||
focusField({ selector, key }) {
|
||||
if (key !== undefined) {
|
||||
this.errors.clear(key);
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
$(`${selector}`).trigger("focus");
|
||||
});
|
||||
},
|
||||
|
||||
regenerateVariationsAndVariantsUid() {
|
||||
this.regenerateVariationsUid();
|
||||
|
||||
const variations = this.getFilteredVariations();
|
||||
const newVariants = this.generateNewVariants(variations);
|
||||
|
||||
newVariants.forEach(({ uids }, index) => {
|
||||
this.$set(this.form.variants[index], "uid", md5(uids));
|
||||
this.$set(this.form.variants[index], "uids", uids);
|
||||
});
|
||||
},
|
||||
|
||||
hasAnyError({ name, uid }) {
|
||||
return Object.keys(this.errors.errors).some((key) =>
|
||||
key.startsWith(`${name}.${uid}`)
|
||||
);
|
||||
},
|
||||
|
||||
clearErrors({ name, uid }) {
|
||||
this.clearMatchedErrors(`${name}.${uid}`);
|
||||
},
|
||||
|
||||
clearValuesError({ name, uid }) {
|
||||
this.clearMatchedErrors(`${name}.${uid}.values`);
|
||||
},
|
||||
|
||||
clearValueRowErrors({ name, uid, valueUid }) {
|
||||
this.clearMatchedErrors(`${name}.${uid}.values.${valueUid}`);
|
||||
},
|
||||
|
||||
clearMatchedErrors(str) {
|
||||
Object.keys(this.errors.errors).forEach((key) => {
|
||||
if (key.startsWith(str)) {
|
||||
this.errors.clear(key);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
scrollToFirstErrorField(elements) {
|
||||
this.$nextTick(() => {
|
||||
[...elements]
|
||||
.find(
|
||||
(el) => el.name === Object.keys(this.errors.errors)[0]
|
||||
)
|
||||
.focus();
|
||||
});
|
||||
},
|
||||
|
||||
addMedia() {
|
||||
const picker = new MediaPicker({ type: "image", multiple: true });
|
||||
|
||||
picker.on("select", ({ id, path }) => {
|
||||
this.form.media.push({
|
||||
id: +id,
|
||||
path,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeMedia(index) {
|
||||
this.form.media.splice(index, 1);
|
||||
},
|
||||
|
||||
preventLastSlideDrag(event) {
|
||||
return event.related.className.indexOf("disabled") === -1;
|
||||
},
|
||||
|
||||
toggleAccordions({ selector, state, data }) {
|
||||
const event = new Event("click");
|
||||
const elements = document.querySelectorAll(selector);
|
||||
|
||||
if (!state) {
|
||||
data.forEach(({ is_open }, index) => {
|
||||
if (is_open) {
|
||||
elements[index].dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[...elements].forEach((element) => {
|
||||
element.dispatchEvent(event);
|
||||
});
|
||||
},
|
||||
|
||||
toggleAccordion(event, data) {
|
||||
const target = $(event.currentTarget);
|
||||
const panelTitle = target.find('[data-toggle="collapse"]');
|
||||
const panelBody = target.next();
|
||||
|
||||
if (data.is_open) {
|
||||
panelBody.css("display", "block");
|
||||
}
|
||||
|
||||
panelTitle.attr("data-transition", true);
|
||||
|
||||
this.$set(data, "is_open", !data.is_open);
|
||||
|
||||
panelBody.slideToggle(300, () => {
|
||||
panelTitle.attr("data-transition", false);
|
||||
panelBody.removeAttr("style");
|
||||
});
|
||||
},
|
||||
|
||||
setSearchableSelectizeConfig() {
|
||||
this.searchableSelectizeConfig = {
|
||||
valueField: "id",
|
||||
labelField: "name",
|
||||
searchField: "name",
|
||||
load: function (query, callback) {
|
||||
var url = route("admin.products.index");
|
||||
|
||||
if (url === undefined || query.length === 0) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
$.get(url + "?query=" + query, callback, "json");
|
||||
},
|
||||
plugins: ["remove_button"],
|
||||
};
|
||||
},
|
||||
|
||||
initTinyMCE() {
|
||||
tinyMCE.baseURL = `${FleetCart.baseUrl}/build/assets/tinymce`;
|
||||
|
||||
tinyMCE.init({
|
||||
selector: ".wysiwyg",
|
||||
theme: "silver",
|
||||
height: 350,
|
||||
menubar: false,
|
||||
branding: false,
|
||||
image_advtab: true,
|
||||
automatic_uploads: true,
|
||||
media_alt_source: false,
|
||||
media_poster: false,
|
||||
relative_urls: false,
|
||||
toolbar_mode: "sliding", // Possible values: floating, sliding, scrolling, wrap
|
||||
directionality: FleetCart.rtl ? "rtl" : "ltr",
|
||||
cache_suffix: `?v=${FleetCart.version}`,
|
||||
plugins:
|
||||
"lists, link, table, image, media, paste, autosave, autolink,quickbars, wordcount, code, fullscreen",
|
||||
toolbar:
|
||||
"styleselect | bold italic underline strikethrough blockquote | bullist numlist | alignleft aligncenter alignright alignjustify | outdent indent | forecolor removeformat | table | image media link | code fullscreen",
|
||||
quickbars_selection_toolbar:
|
||||
"bold italic | quicklink h2 h3 blockquote quickimage quicktable",
|
||||
setup: (editor) => {
|
||||
editor.on("change", () => {
|
||||
editor.save();
|
||||
editor.getElement().dispatchEvent(new Event("input"));
|
||||
|
||||
this.errors.clear("description");
|
||||
});
|
||||
},
|
||||
images_upload_handler: (blobInfo, success, failure) => {
|
||||
let formData = new FormData();
|
||||
|
||||
formData.append(
|
||||
"file",
|
||||
blobInfo.blob(),
|
||||
blobInfo.filename()
|
||||
);
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: route("admin.media.store"),
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
})
|
||||
.then((file) => {
|
||||
success(file.path);
|
||||
})
|
||||
.catch((xhr) => {
|
||||
failure(xhr.responseJSON.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
400
Modules/Product/Resources/assets/admin/js/mixins/VariantMixin.js
Normal file
400
Modules/Product/Resources/assets/admin/js/mixins/VariantMixin.js
Normal file
@@ -0,0 +1,400 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
defaultVariantUid: "",
|
||||
variantPosition: 0,
|
||||
variantsLength: 0,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasAnyVariant() {
|
||||
return this.form.variants.length !== 0;
|
||||
},
|
||||
|
||||
isCollapsedVariantsAccordion() {
|
||||
return this.form.variants.every(({ is_open }) => is_open === false);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareVariants(variants) {
|
||||
variants.forEach((variant) => {
|
||||
this.$set(variant, "position", this.variantPosition++);
|
||||
this.$set(variant, "is_open", false);
|
||||
this.$set(variant, "is_selected", false);
|
||||
});
|
||||
},
|
||||
|
||||
changeDefaultVariant(uid) {
|
||||
const variants = this.form.variants;
|
||||
const index = variants.findIndex((variant) => variant.uid === uid);
|
||||
|
||||
if (variants[index].is_active === true) {
|
||||
this.resetDefaultVariant();
|
||||
this.defaultVariantUid = variants[index].uid;
|
||||
this.$set(variants[index], "is_default", true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.defaultVariantUid = this.defaultVariantUid;
|
||||
},
|
||||
|
||||
setDefaultVariant() {
|
||||
const variants = this.form.variants;
|
||||
const index = variants.findIndex(
|
||||
({ uid }) => uid === this.defaultVariantUid
|
||||
);
|
||||
|
||||
this.resetDefaultVariant();
|
||||
|
||||
const variant = variants[index === -1 ? 0 : index];
|
||||
|
||||
this.defaultVariantUid = variant.uid;
|
||||
this.$set(variant, "is_default", true);
|
||||
|
||||
if (index === -1) {
|
||||
this.defaultVariantUid = variants[0].uid;
|
||||
this.$set(variants[0], "is_active", true);
|
||||
}
|
||||
},
|
||||
|
||||
isActiveVariant(index) {
|
||||
return this.form.variants[index].is_active;
|
||||
},
|
||||
|
||||
changeVariantStatus(variantUid) {
|
||||
if (this.defaultVariantUid === variantUid) {
|
||||
toaster(
|
||||
trans("product::products.variants.disable_default_variant"),
|
||||
{
|
||||
type: "default",
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearErrors({
|
||||
name: "variants",
|
||||
uid: variantUid,
|
||||
});
|
||||
},
|
||||
|
||||
resetDefaultVariant() {
|
||||
this.form.variants.some((variant) => {
|
||||
if (variant.is_default === true) {
|
||||
this.$set(variant, "is_default", false);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getFilteredVariations() {
|
||||
return this.form.variations
|
||||
.map(({ type, values }) =>
|
||||
values
|
||||
.map(({ uid, label }) => {
|
||||
if (type !== "" && Boolean(label)) {
|
||||
return { uid, label };
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
)
|
||||
.filter((data) => data.length !== 0);
|
||||
},
|
||||
|
||||
generateNewVariants(variations) {
|
||||
return variations
|
||||
.reduce((accumulator, currentValue) =>
|
||||
accumulator.flatMap((x) =>
|
||||
currentValue.map((y) => {
|
||||
return {
|
||||
uid: x.uid + "." + y.uid,
|
||||
label: x.label + " / " + y.label,
|
||||
};
|
||||
})
|
||||
)
|
||||
)
|
||||
.map(({ uid, label }) => {
|
||||
return {
|
||||
uids: uid.split(".").sort().join("."),
|
||||
name: label,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
generateVariants() {
|
||||
this.$nextTick(() => {
|
||||
this.initColorPicker();
|
||||
this.updateColorThumbnails();
|
||||
});
|
||||
|
||||
// Filter empty variation values
|
||||
const variations = this.getFilteredVariations();
|
||||
|
||||
if (variations.length === 0) {
|
||||
this.form.variants = [];
|
||||
this.variantsLength = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const newVariants = this.generateNewVariants(variations);
|
||||
const oldVariants = this.form.variants.map(({ uids }) => {
|
||||
return {
|
||||
uids,
|
||||
};
|
||||
});
|
||||
|
||||
if (newVariants.length > this.variantsLength) {
|
||||
// Variation added
|
||||
this.addVariants(newVariants, oldVariants);
|
||||
} else if (newVariants.length < this.variantsLength) {
|
||||
// Variation removed
|
||||
this.removeVariants(newVariants, oldVariants);
|
||||
} else if (newVariants.length === this.variantsLength) {
|
||||
// Variations reordered
|
||||
this.reorderVariants(newVariants, oldVariants);
|
||||
}
|
||||
|
||||
this.variantsLength = newVariants.length;
|
||||
this.setDefaultVariant();
|
||||
},
|
||||
|
||||
addVariants(newVariants, oldVariants) {
|
||||
this.notifyVariantsCreated(newVariants.length);
|
||||
|
||||
// Add initial variation with single or multiple values when variants are empty
|
||||
if (oldVariants.length === 0) {
|
||||
newVariants.forEach((newVariant) => {
|
||||
this.form.variants.push(
|
||||
this.variantDefaultData(newVariant)
|
||||
);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// A new single value has been added with existing variation values
|
||||
if (this.hasCommonVariantUids(newVariants, oldVariants)) {
|
||||
const oldVariantsUids = oldVariants.map(({ uids }) => uids);
|
||||
|
||||
newVariants.forEach((newVariant, index) => {
|
||||
if (!oldVariantsUids.includes(newVariant.uids)) {
|
||||
this.form.variants.splice(
|
||||
index,
|
||||
0,
|
||||
this.variantDefaultData(newVariant)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// A new variation with multiple values has been added
|
||||
const matchedUids = [];
|
||||
|
||||
oldVariants.forEach(({ uids }) => {
|
||||
newVariants.forEach((newVariant, index) => {
|
||||
const doesUidExist = uids
|
||||
.split(".")
|
||||
.every((uids) =>
|
||||
newVariant.uids.split(".").includes(uids)
|
||||
);
|
||||
|
||||
if (doesUidExist && !matchedUids.includes(uids)) {
|
||||
matchedUids.push(uids);
|
||||
this.setVariantData(newVariant, index);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (doesUidExist) {
|
||||
this.form.variants.splice(
|
||||
index,
|
||||
0,
|
||||
this.variantDefaultData(newVariant)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeVariants(newVariants, oldVariants) {
|
||||
this.resetBulkEditVariantFields();
|
||||
this.notifyVariantsRemoved(oldVariants.length - newVariants.length);
|
||||
|
||||
// Variation single value has been removed
|
||||
if (this.hasCommonVariantUids(newVariants, oldVariants)) {
|
||||
const newVariantsUids = newVariants.map(({ uids }) => uids);
|
||||
|
||||
oldVariants.forEach(({ uids }) => {
|
||||
if (!newVariantsUids.includes(uids)) {
|
||||
const index = this.form.variants.findIndex(
|
||||
(variant) => variant.uids === uids
|
||||
);
|
||||
|
||||
this.clearErrors({
|
||||
name: "variants",
|
||||
uid: this.form.variants[index].uid,
|
||||
});
|
||||
this.form.variants.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// A variation with multiple values has been removed
|
||||
const matchedUids = [];
|
||||
|
||||
newVariants.forEach(({ uids, name }) => {
|
||||
oldVariants.forEach((oldVariant) => {
|
||||
const index = this.form.variants.findIndex(
|
||||
(variant) => variant.uids === oldVariant.uids
|
||||
);
|
||||
const doesUidExist = uids
|
||||
.split(".")
|
||||
.every((uids) =>
|
||||
oldVariant.uids.split(".").includes(uids)
|
||||
);
|
||||
|
||||
if (doesUidExist && !matchedUids.includes(uids)) {
|
||||
matchedUids.push(uids);
|
||||
this.setVariantData({ uids, name }, index);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (doesUidExist) {
|
||||
this.clearErrors({
|
||||
name: "variants",
|
||||
uid: this.form.variants[index].uid,
|
||||
});
|
||||
this.form.variants.splice(index, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
reorderVariants(newVariants, oldVariants) {
|
||||
// Reordered variations or variation values
|
||||
const newVariantUids = newVariants.map(({ uids }) => uids);
|
||||
|
||||
if (this.hasCommonVariantUids(newVariants, oldVariants)) {
|
||||
oldVariants.forEach(({ uids }) => {
|
||||
const index = newVariantUids.indexOf(uids);
|
||||
const formIndex = this.form.variants.findIndex(
|
||||
(variant) => variant.uids === uids
|
||||
);
|
||||
|
||||
// Update variant data before swap
|
||||
this.setVariantData(
|
||||
{ name: newVariants[index].name },
|
||||
formIndex
|
||||
);
|
||||
|
||||
// Swap variant elements
|
||||
this.form.variants[formIndex] = this.form.variants.splice(
|
||||
index,
|
||||
1,
|
||||
this.form.variants[formIndex]
|
||||
)[0];
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// A new variation with a single value has been added
|
||||
newVariants.forEach((newVariant, index) => {
|
||||
this.setVariantData(newVariant, index);
|
||||
});
|
||||
},
|
||||
|
||||
hasCommonVariantUids(newVariants, oldVariants) {
|
||||
// Check if the old variants UID is present in the new variants
|
||||
return oldVariants.some(({ uids }) =>
|
||||
newVariants.map(({ uids }) => uids).includes(uids)
|
||||
);
|
||||
},
|
||||
|
||||
setVariantData({ uids, name }, index) {
|
||||
if (uids !== undefined) {
|
||||
this.$set(this.form.variants[index], "uid", md5(uids));
|
||||
this.$set(this.form.variants[index], "uids", uids);
|
||||
}
|
||||
|
||||
this.$set(this.form.variants[index], "name", name);
|
||||
},
|
||||
|
||||
variantDefaultData({ uids, name }) {
|
||||
return {
|
||||
position: this.variantPosition++,
|
||||
uid: md5(uids),
|
||||
uids,
|
||||
name,
|
||||
media: [],
|
||||
is_active: true,
|
||||
is_open: false,
|
||||
is_default: false,
|
||||
is_selected: false,
|
||||
special_price_type: "fixed",
|
||||
manage_stock: 0,
|
||||
in_stock: 1,
|
||||
};
|
||||
},
|
||||
|
||||
resetVariants() {
|
||||
this.form.variants = [];
|
||||
},
|
||||
|
||||
addVariantMedia(index) {
|
||||
const picker = new MediaPicker({ type: "image", multiple: true });
|
||||
|
||||
picker.on("select", ({ id, path }) => {
|
||||
this.form.variants[index].media.push({
|
||||
id: +id,
|
||||
path,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeVariantMedia(variantIndex, mediaIndex) {
|
||||
this.form.variants[variantIndex].media.splice(mediaIndex, 1);
|
||||
},
|
||||
|
||||
notifyVariantChanges({ count, status }) {
|
||||
toaster(
|
||||
trans(`product::products.variants.variants_${status}`, {
|
||||
count,
|
||||
suffix: trans(
|
||||
`product::products.variants.${
|
||||
count > 1 ? "variants" : "variant"
|
||||
}`
|
||||
),
|
||||
}).toLowerCase(),
|
||||
{
|
||||
type: "default",
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
notifyVariantsCreated(count) {
|
||||
this.notifyVariantChanges({ count, status: "created" });
|
||||
},
|
||||
|
||||
notifyVariantsRemoved(count) {
|
||||
this.notifyVariantChanges({ count, status: "removed" });
|
||||
},
|
||||
|
||||
notifyVariantsReordered() {
|
||||
toaster(trans("product::products.variants.variants_reordered"), {
|
||||
type: "default",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,316 @@
|
||||
import Coloris from "@melloware/coloris";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
globalVariationId: "",
|
||||
addingGlobalVariation: false,
|
||||
variations: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isAddGlobalVariationDisabled() {
|
||||
return this.globalVariationId === "" || this.addingGlobalVariation;
|
||||
},
|
||||
|
||||
isCollapsedVariationsAccordion() {
|
||||
return this.form.variations.every(
|
||||
({ is_open }) => is_open === false
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
"form.variations": {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (newValue.length === 0) {
|
||||
this.addVariation({ preventFocus: true });
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initColorPicker();
|
||||
this.hideColorPicker();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareVariations(variations) {
|
||||
variations.forEach((variation) => {
|
||||
this.$set(variation, "is_open", false);
|
||||
});
|
||||
},
|
||||
|
||||
regenerateVariationsUid() {
|
||||
this.form.variations.forEach((variation) => {
|
||||
this.$set(variation, "uid", this.uid());
|
||||
|
||||
variation.values.forEach((_, valueIndex) => {
|
||||
this.$set(variation.values[valueIndex], "uid", this.uid());
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
reorderVariations() {
|
||||
this.generateVariants();
|
||||
this.notifyVariantsReordered();
|
||||
},
|
||||
|
||||
reorderVariationValues() {
|
||||
this.generateVariants();
|
||||
this.notifyVariantsReordered();
|
||||
},
|
||||
|
||||
addVariation({ preventFocus }) {
|
||||
const uid = this.uid();
|
||||
|
||||
this.form.variations.push({
|
||||
uid,
|
||||
type: "",
|
||||
is_global: false,
|
||||
is_open: true,
|
||||
values: [
|
||||
{
|
||||
uid: this.uid(),
|
||||
image: {
|
||||
id: null,
|
||||
path: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!preventFocus) {
|
||||
console.log("add");
|
||||
this.focusField({
|
||||
selector: `#variations-${uid}-name`,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
deleteVariation(index, uid) {
|
||||
this.form.variations.splice(index, 1);
|
||||
this.clearErrors({ name: "variations", uid });
|
||||
this.generateVariants();
|
||||
},
|
||||
|
||||
changeVariationType(value, index, uid) {
|
||||
const variation = this.form.variations[index];
|
||||
|
||||
if (value !== "" && variation.values.length === 1) {
|
||||
this.focusField({
|
||||
selector: `#variations-${uid}-values-${variation.values[0].uid}-label`,
|
||||
});
|
||||
}
|
||||
|
||||
if (value === "text") {
|
||||
variation.values.forEach((value) => {
|
||||
this.errors.clear([
|
||||
`variations.${uid}.values.${value.uid}.color`,
|
||||
`variations.${uid}.values.${value.uid}.image`,
|
||||
]);
|
||||
});
|
||||
} else if (value === "color") {
|
||||
variation.values.forEach((value) => {
|
||||
this.errors.clear(
|
||||
`variations.${uid}.values.${value.uid}.image`
|
||||
);
|
||||
});
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.initColorPicker();
|
||||
});
|
||||
} else if (value === "image") {
|
||||
variation.values.forEach((value, valueIndex) => {
|
||||
if (!value.image) {
|
||||
this.$set(variation.values[valueIndex], "image", {
|
||||
id: null,
|
||||
path: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
variation.values.forEach((value) => {
|
||||
this.errors.clear(
|
||||
`variations.${uid}.values.${value.uid}.color`
|
||||
);
|
||||
});
|
||||
} else {
|
||||
this.clearValuesError(index, uid);
|
||||
}
|
||||
},
|
||||
|
||||
chooseVariationImage(
|
||||
variationIndex,
|
||||
variationUid,
|
||||
valueIndex,
|
||||
valueUid
|
||||
) {
|
||||
let picker = new MediaPicker({ type: "image" });
|
||||
|
||||
picker.on("select", ({ id, path }) => {
|
||||
this.form.variations[variationIndex].values[valueIndex].image =
|
||||
{
|
||||
id: +id,
|
||||
path,
|
||||
};
|
||||
|
||||
this.errors.clear(
|
||||
`variations.${variationUid}.values.${valueUid}.image`
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
addVariationRow(index, variationUid) {
|
||||
const valueUid = this.uid();
|
||||
|
||||
this.form.variations[index].values.push({
|
||||
uid: valueUid,
|
||||
image: {
|
||||
id: null,
|
||||
path: null,
|
||||
},
|
||||
});
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.initColorPicker();
|
||||
|
||||
$(
|
||||
`#variations-${variationUid}-values-${valueUid}-label`
|
||||
).trigger("focus");
|
||||
});
|
||||
},
|
||||
|
||||
addVariationRowOnPressEnter(event, variationIndex, valueIndex) {
|
||||
const variation = this.form.variations[variationIndex];
|
||||
const values = variation.values;
|
||||
|
||||
if (event.target.value === "") return;
|
||||
|
||||
if (values.length - 1 === valueIndex) {
|
||||
this.addVariationRow(variationIndex, variation.uid);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueIndex < values.length - 1) {
|
||||
$(
|
||||
`#variations-${variation.uid}-values-${
|
||||
values[valueIndex + 1].uid
|
||||
}-label`
|
||||
).trigger("focus");
|
||||
}
|
||||
},
|
||||
|
||||
deleteVariationRow(variationIndex, variationUid, valueIndex, valueUid) {
|
||||
const variation = this.form.variations[variationIndex];
|
||||
|
||||
variation.values.splice(valueIndex, 1);
|
||||
|
||||
this.clearValueRowErrors({
|
||||
name: "variations",
|
||||
variationUid,
|
||||
valueUid,
|
||||
});
|
||||
|
||||
if (variation.values.length === 0) {
|
||||
this.addVariationRow(variationIndex, variationUid);
|
||||
}
|
||||
|
||||
this.generateVariants();
|
||||
},
|
||||
|
||||
updateColorThumbnails() {
|
||||
this.form.variations.forEach(({ uid, type, values }) => {
|
||||
if (type !== "color") return;
|
||||
|
||||
const elements = document.querySelectorAll(
|
||||
`.variation-${uid} .clr-field`
|
||||
);
|
||||
|
||||
values.forEach(({ color }, valueIndex) => {
|
||||
elements[valueIndex].style.color = color || "";
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
initColorPicker() {
|
||||
Coloris.init();
|
||||
|
||||
Coloris({
|
||||
el: ".color-picker",
|
||||
alpha: false,
|
||||
rtl: FleetCart.rtl,
|
||||
theme: "large",
|
||||
wrap: true,
|
||||
format: "hex",
|
||||
selectInput: true,
|
||||
swatches: [
|
||||
"#D01C1F",
|
||||
"#3AA845",
|
||||
"#118257",
|
||||
"#0A33AE",
|
||||
"#0D46A0",
|
||||
"#000000",
|
||||
"#5F4C3A",
|
||||
"#726E6E",
|
||||
"#F6D100",
|
||||
"#C0E506",
|
||||
"#FF540A",
|
||||
"#C5A996",
|
||||
"#4B80BE",
|
||||
"#A1C3DA",
|
||||
"#C8BFC2",
|
||||
"#A9A270",
|
||||
],
|
||||
});
|
||||
},
|
||||
|
||||
hideColorPicker() {
|
||||
$(document).on("click", "#clr-swatches button", (e) => {
|
||||
$(e.currentTarget)
|
||||
.parents("#clr-picker")
|
||||
.removeClass("clr-open");
|
||||
});
|
||||
},
|
||||
|
||||
addGlobalVariation() {
|
||||
if (this.globalVariationId === "") return;
|
||||
|
||||
this.addingGlobalVariation = true;
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: route("admin.variations.show", this.globalVariationId),
|
||||
dataType: "json",
|
||||
success: (variation) => {
|
||||
variation.uid = this.uid();
|
||||
variation.is_open = true;
|
||||
|
||||
variation.values.forEach((value) => {
|
||||
value.uid = this.uid();
|
||||
});
|
||||
|
||||
this.form.variations.push(variation);
|
||||
this.generateVariants();
|
||||
|
||||
this.$nextTick(() => {
|
||||
$(`#variations-${variation.uid}-name`).trigger("focus");
|
||||
});
|
||||
},
|
||||
})
|
||||
.catch((error) => {
|
||||
toaster(error.responseJSON.message, {
|
||||
type: "error",
|
||||
});
|
||||
})
|
||||
.always(() => {
|
||||
this.globalVariationId = "";
|
||||
this.addingGlobalVariation = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,379 @@
|
||||
#images, #attachments {
|
||||
margin-bottom: 15px;
|
||||
@import "@admin/sass/wysiwyg";
|
||||
|
||||
.v-toast {
|
||||
padding: 20px 20px 60px;
|
||||
}
|
||||
|
||||
.product-form {
|
||||
.box {
|
||||
.box-header {
|
||||
.toggle-accordion {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin: 1px 10px 0 0;
|
||||
padding: 0 5px;
|
||||
color: #333333;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&.collapsed {
|
||||
i {
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
transition: 150ms ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.box-body {
|
||||
> .form-group {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.accordion-box-content {
|
||||
.panel-heading {
|
||||
[data-transition="false"] {
|
||||
&:after {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.accordion-box-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.insert-template {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
select {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 20%);
|
||||
grid-template-rows: 1fr 1fr;
|
||||
margin: -6px;
|
||||
}
|
||||
|
||||
.media-grid-item {
|
||||
margin: 6px;
|
||||
border: 1px solid #d2d6de;
|
||||
border-radius: 3px;
|
||||
|
||||
&:first-child {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 3;
|
||||
}
|
||||
|
||||
&.handle {
|
||||
.image-holder {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
&.media-picker {
|
||||
border-width: 2px;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.image-holder {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding-bottom: 90%;
|
||||
border: none;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
.variants {
|
||||
position: relative;
|
||||
|
||||
.fade {
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
position: absolute;
|
||||
transition: 150ms ease-in-out;
|
||||
}
|
||||
|
||||
&-enter,
|
||||
&-leave-to {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bulk-edit-variants {
|
||||
.product-media-grid {
|
||||
margin-bottom: -6px;
|
||||
}
|
||||
}
|
||||
|
||||
.variants-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.panel-group {
|
||||
width: 100%;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
[data-toggle="collapse"] {
|
||||
padding: 12px 15px 13px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
padding-top: 0;
|
||||
|
||||
label {
|
||||
min-height: 18px;
|
||||
}
|
||||
|
||||
[type="checkbox"]:checked + label:before,
|
||||
[type="checkbox"]:not(:checked) + label:before {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
[type="checkbox"]:checked + label:after,
|
||||
[type="checkbox"]:not(:checked) + label:after {
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.switch {
|
||||
display: inline-flex;
|
||||
margin-right: 25px;
|
||||
padding-top: 0;
|
||||
|
||||
[type="checkbox"] {
|
||||
&:checked + label::after {
|
||||
left: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
padding-left: 30px;
|
||||
|
||||
&:before {
|
||||
top: 5px;
|
||||
height: 10px;
|
||||
width: 26px;
|
||||
}
|
||||
|
||||
&:after {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
> .row {
|
||||
> .col-sm-4 {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
> .col-sm-8 {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.variant-fields {
|
||||
margin-bottom: -10px;
|
||||
overflow: hidden;
|
||||
|
||||
> .row {
|
||||
> .col-sm-6 {
|
||||
&:nth-child(1) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.variant-name {
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.variant-badge {
|
||||
margin-left: 5px;
|
||||
|
||||
li {
|
||||
display: inline-flex;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 11px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.variant {
|
||||
&-enter-active,
|
||||
&-leave-active,
|
||||
&-move {
|
||||
transition: 150ms ease-in-out;
|
||||
}
|
||||
|
||||
&-enter,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
&-enter-from {
|
||||
opacity: 0;
|
||||
scale: 0;
|
||||
}
|
||||
|
||||
&-enter-to {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
&-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(4, 25%);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.product-form-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 10px 20px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.18);
|
||||
transition: 150ms ease-in-out;
|
||||
z-index: 10;
|
||||
|
||||
.btn {
|
||||
margin-right: 10px;
|
||||
padding: 6px 14px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
border: 1px solid #d9d9d9;
|
||||
background: #ffffff;
|
||||
|
||||
&:hover {
|
||||
color: #0068e1;
|
||||
border-color: #0074fb;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
color: #0068e1;
|
||||
background: #ffffff;
|
||||
border: 1px solid #0068e1;
|
||||
|
||||
&.btn-loading {
|
||||
&:after {
|
||||
border-color: #0068e1;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:after {
|
||||
border-color: #ffffff;
|
||||
border-right-color: #0068e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #ffffff;
|
||||
background: #0068e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form-left-column {
|
||||
padding-right: 10px;
|
||||
|
||||
.box {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form-right-column {
|
||||
padding-left: 10px;
|
||||
|
||||
.box {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form-column {
|
||||
&.dragging {
|
||||
border-radius: 2px;
|
||||
outline: 2px dashed #a3b0c2;
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-wrapper {
|
||||
@@ -20,3 +394,350 @@
|
||||
padding: 10px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.product-downloads-wrapper {
|
||||
.slide {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table {
|
||||
> tbody {
|
||||
> tr {
|
||||
> td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
.drag-handle {
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.choose-file-group {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.downloadable-file-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.btn-choose-file {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.ltr {
|
||||
&.sidebar-collapse {
|
||||
.product-form {
|
||||
.product-form-footer {
|
||||
left: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form {
|
||||
.product-form-footer {
|
||||
left: 250px;
|
||||
right: 0;
|
||||
|
||||
.btn-default {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rtl {
|
||||
&.sidebar-collapse {
|
||||
.product-form {
|
||||
.product-form-footer {
|
||||
right: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form {
|
||||
.product-form-footer {
|
||||
right: 250px;
|
||||
left: 0;
|
||||
|
||||
.btn-default {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px) and (max-width: 1649px) {
|
||||
.product-form-right-column {
|
||||
.box-body {
|
||||
> .form-group {
|
||||
margin-bottom: 15px;
|
||||
|
||||
> .col-sm-3,
|
||||
> .col-sm-9 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 5px;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1650px) {
|
||||
.product-form {
|
||||
.variants-group {
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(3, 33.33333333333333%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form-right-column {
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(4, 25%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1500px) {
|
||||
.product-form {
|
||||
.bulk-edit-variants {
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(4, 25%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1380px) {
|
||||
.product-form-right-column {
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(3, 33.33333333333333%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1199px) {
|
||||
.product-form-left-column {
|
||||
padding-right: 15px;
|
||||
|
||||
.box {
|
||||
&:last-child {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form-right-column {
|
||||
padding-left: 15px;
|
||||
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(8, 12.5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 991px) {
|
||||
.form-horizontal {
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.control-label {
|
||||
margin-bottom: 0;
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox,
|
||||
.switch {
|
||||
padding-top: 8px;
|
||||
|
||||
label {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form {
|
||||
.product-form-footer {
|
||||
position: relative;
|
||||
bottom: auto;
|
||||
margin-top: 20px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ltr,
|
||||
.rtl {
|
||||
.product-form {
|
||||
.product-form-footer {
|
||||
left: auto;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.form-horizontal {
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
|
||||
.control-label {
|
||||
margin-bottom: 6px;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox,
|
||||
.switch {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.product-form {
|
||||
.bulk-edit-variants {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bulk-edit-variants,
|
||||
.variants-group {
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(7, 14.28571428571429%);
|
||||
}
|
||||
}
|
||||
|
||||
.variants-group {
|
||||
.panel-body {
|
||||
> .row {
|
||||
> .col-sm-4 {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
> .col-sm-8 {
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.variant-fields {
|
||||
margin-top: 15px;
|
||||
|
||||
> .row {
|
||||
> .col-sm-6 {
|
||||
&:nth-child(1) {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form-right-column {
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(6, 16.66666666666667%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 650px) {
|
||||
.product-form {
|
||||
.bulk-edit-variants,
|
||||
.variants-group {
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(6, 16.66666666666667%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 560px) {
|
||||
.product-form .bulk-edit-variants,
|
||||
.product-form .variants-group,
|
||||
.product-form-right-column {
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(5, 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 420px) {
|
||||
.product-form .bulk-edit-variants,
|
||||
.product-form .variants-group,
|
||||
.product-form-right-column {
|
||||
.product-media-grid {
|
||||
grid-template-columns: repeat(4, 25%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.box-header {
|
||||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
.box-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.product-downloads-wrapper {
|
||||
.table {
|
||||
> tbody {
|
||||
> tr {
|
||||
> td {
|
||||
&:nth-child(2) {
|
||||
min-width: 280px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 435px) {
|
||||
.product-form {
|
||||
.accordion-box-content {
|
||||
.accordion-box-footer {
|
||||
flex-direction: column;
|
||||
|
||||
> .btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 370px) {
|
||||
.product-form {
|
||||
.product-form-footer {
|
||||
flex-direction: column;
|
||||
|
||||
.btn {
|
||||
margin-right: 0;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user