Enhancement #4250 done for default theme

This commit is contained in:
rahulshukla-home 2020-11-19 15:23:51 +05:30
parent 25b2603a2e
commit 2c9b131fd3
24 changed files with 548 additions and 17 deletions

View File

@ -529,7 +529,13 @@ return [
'checkbox' => 'خانة اختيار',
'multiselect' => 'تحديد متعدد',
'new-option' => 'خيار جديد',
'is-default' => 'افتراضي'
'is-default' => 'افتراضي',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' => [

View File

@ -521,6 +521,12 @@ return [
'multiselect' => 'Multiselect',
'new-option' => 'Neue Option',
'is-default' => 'Ist Standard',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' =>
[

View File

@ -533,6 +533,12 @@ return [
'all-group' => 'All Groups',
'fixed' => 'Fixed',
'discount' => 'Discount',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' => [

View File

@ -522,7 +522,13 @@ return [
'checkbox' => 'Opción múltiple',
'multiselect' => 'Lista de selección múltiple',
'new-option' => 'Nueva opción',
'is-default' => 'Es predeterminada'
'is-default' => 'Es predeterminada',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' => [

View File

@ -527,7 +527,13 @@ return [
'checkbox' => 'Checkbox',
'multiselect' => 'Multiselect',
'new-option' => 'New Option',
'is-default' => 'Is Default'
'is-default' => 'Is Default',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' => [

View File

@ -530,7 +530,13 @@ return [
'multiselect' => 'Multiselect',
'new-option' => 'Nuova Opzione',
'is-default' => 'È Predefinita',
'actions' => 'Azioni'
'actions' => 'Azioni',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' => [

View File

@ -529,6 +529,12 @@ return [
'multiselect' => 'Multiselect',
'new-option' => 'Nieuwe optie',
'is-default' => 'is standaard',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' => [

View File

@ -528,7 +528,13 @@ return [
'checkbox' => 'Pole wyboru',
'multiselect' => 'Wielokrotny wybór',
'new-option' => 'Nowa opcja',
'is-default' => 'Jest domyślny'
'is-default' => 'Jest domyślny',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' => [

View File

@ -527,7 +527,13 @@ return [
'checkbox' => 'Checkbox',
'multiselect' => 'Multiselect',
'new-option' => 'New Option',
'is-default' => 'Is Default'
'is-default' => 'Is Default',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' => [

View File

@ -523,7 +523,13 @@ return [
'checkbox' => 'Onay Kutusu',
'multiselect' => 'Çoklu Seçim',
'new-option' => 'Yeni Seçenek',
'is-default' => 'Varsayılan'
'is-default' => 'Varsayılan',
'remove-image-btn-title' => 'Remove Image',
'videos' => 'Videos',
'video' => 'Video',
'add-video-btn-title' => 'Add Video',
'remove-video-btn-title' => 'Remove Video',
'not-support-video' => 'Your browser does not support the video tag.',
],
'attributes' => [

View File

@ -54,7 +54,9 @@
<img class="preview" :src="imageData" v-if="imageData.length > 0">
<label class="remove-image" @click="removeImage()">Remove Image</label>
<label class="remove-image" @click="removeImage()">
{{ __('admin::app.catalog.products.remove-image-btn-title') }}
</label>
</label>
</script>

View File

@ -0,0 +1,198 @@
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.videos.before', ['product' => $product]) !!}
<accordian :title="'{{ __('admin::app.catalog.products.videos') }}'" :active="false">
<div slot="body">
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.videos.controls.before', ['product' => $product]) !!}
<div class="control-group {!! $errors->has('videos.*') ? 'has-error' : '' !!}">
<label>{{ __('admin::app.catalog.products.video') }}</label>
<product-video></product-video>
<span class="control-error" v-if="{!! $errors->has('videos.*') !!}">
@php $count=1 @endphp
@foreach ($errors->get('videos.*') as $key => $message)
@php echo str_replace($key, 'Video'.$count, $message[0]); $count++ @endphp
@endforeach
</span>
</div>
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.videos.controls.after', ['product' => $product]) !!}
</div>
</accordian>
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.videos.after', ['product' => $product]) !!}
@push('scripts')
@parent
<script type="text/x-template" id="product-video-template">
<div>
<div class="image-wrapper">
<product-video-item
v-for='(video, index) in items'
:key='video.id'
:video="video"
@onRemoveVideo="removeVideo($event)"
@onVideoSelected="videoSelected($event)"
></product-video-item>
</div>
<label class="btn btn-lg btn-primary" style="display: inline-block; width: auto" @click="createFileType">
{{ __('admin::app.catalog.products.add-video-btn-title') }}
</label>
</div>
</script>
<script type="text/x-template" id="product-video-item-template">
<label class="image-item" v-bind:class="{ 'has-image': videoData.length > 0 }">
<input type="hidden" :name="'videos[' + video.id + ']'" v-if="! new_video"/>
<input type="file" accept="video/*" :name="'videos[]'" ref="videoInput" :id="_uid" @change="addVideoView($event)" multiple="multiple"/>
<video class="preview" v-if="videoData.length > 0" width="200" height="160" controls>
<source :src="videoData" type="video/mp4">
{{ __('admin::app.catalog.products.not-support-video') }}
</video>
<label class="remove-image" @click="removeVideo()">
{{ __('admin::app.catalog.products.remove-video-btn-title') }}
</label>
</label>
</script>
<script>
Vue.component('product-video', {
template: '#product-video-template',
data: function() {
return {
videos: @json($product->videos),
videoCount: 0,
items: []
}
},
computed: {
finalInputName: function() {
return 'videos[' + this.video.id + ']';
}
},
created: function() {
var this_this = this;
this.videos.forEach(function(video) {
this_this.items.push(video)
this_this.videoCount++;
});
},
methods: {
createFileType: function() {
var this_this = this;
this.videoCount++;
this.items.push({'id': 'video_' + this.videoCount});
},
removeVideo (video) {
let index = this.items.indexOf(video)
Vue.delete(this.items, index);
},
videoSelected: function(event) {
var this_this = this;
Array.from(event.files).forEach(function(video, index) {
if (index) {
this_this.videoCount++;
this_this.items.push({'id': 'video_' + this_this.videoCount, file: video});
}
});
}
}
});
Vue.component('product-video-item', {
template: '#product-video-item-template',
props: {
video: {
type: Object,
required: false,
default: null
},
},
data: function() {
return {
videoData: '',
new_video: 0
}
},
mounted () {
if (this.video.id && this.video.url) {
this.videoData = this.video.url;
} else if (this.video.id && this.video.file) {
this.readFile(this.video.file);
}
},
computed: {
finalInputName: function() {
return this.inputName + '[' + this.video.id + ']';
}
},
methods: {
addVideoView: function() {
var videoInput = this.$refs.videoInput;
if (videoInput.files && videoInput.files[0]) {
if (videoInput.files[0].type.includes('video/')) {
this.readFile(videoInput.files[0])
if (videoInput.files.length > 1) {
this.$emit('onVideoSelected', videoInput)
}
} else {
videoInput.value = "";
alert('Only videos (.mp4, .mov, .ogg ..) are allowed.');
}
}
},
readFile: function(video) {
var reader = new FileReader();
reader.onload = (e) => {
this.videoData = e.target.result;
}
reader.readAsDataURL(video);
this.new_video = 1;
},
removeVideo: function() {
this.$emit('onRemoveVideo', this.video)
}
}
});
</script>
@endpush

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\Product\Contracts;
interface ProductVideo
{
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProductVideosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('product_videos', function (Blueprint $table) {
$table->increments('id');
$table->string('type')->nullable();
$table->string('path');
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('product_videos');
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Webkul\Product\Helpers;
use Illuminate\Support\Facades\Storage;
class ProductVideo extends AbstractProduct
{
/**
* Retrieve collection of videos
*
* @param \Webkul\Product\Contracts\Product|\Webkul\Product\Contracts\ProductFlat $product
* @return array
*/
public function getVideos($product)
{
if (! $product) {
return [];
}
$videos = [];
foreach ($product->videos as $video) {
if (! Storage::has($video->path)) {
continue;
}
$videos[] = [
'type' => $video->type,
'video_url' => Storage::url($video->path),
];
}
return $videos;
}
}

View File

@ -143,6 +143,14 @@ class Product extends Model implements ProductContract
return $this->hasMany(ProductImageProxy::modelClass(), 'product_id');
}
/**
* The videos that belong to the product.
*/
public function videos()
{
return $this->hasMany(ProductVideoProxy::modelClass(), 'product_id');
}
/**
* The images that belong to the product.
*/

View File

@ -126,6 +126,14 @@ class ProductFlat extends Model implements ProductFlatContract
->select('product_images.*');
}
/**
* The videos that belong to the product.
*/
public function videos()
{
return $this->product->videos();
}
/**
* Get all of the attributes for the attribute groups.
*/

View File

@ -0,0 +1,64 @@
<?php
namespace Webkul\Product\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
use Webkul\Product\Contracts\ProductVideo as ProductVideoContract;
class ProductVideo extends Model implements ProductVideoContract
{
public $timestamps = false;
protected $fillable = [
'path',
'product_id',
'type',
];
/**
* Get the product that owns the image.
*/
public function product()
{
return $this->belongsTo(ProductProxy::modelClass());
}
/**
* Get image url for the product image.
*/
public function url()
{
return Storage::url($this->path);
}
/**
* Get image url for the product image.
*/
public function getUrlAttribute()
{
return $this->url();
}
/**
* @param string $key
*
* @return bool
*/
public function isCustomAttribute($attribute)
{
return $this->attribute_family->custom_attributes->pluck('code')->contains($attribute);
}
/**
* @return array
*/
public function toArray()
{
$array = parent::toArray();
$array['url'] = $this->url;
return $array;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\Product\Models;
use Konekt\Concord\Proxies\ModelProxy;
class ProductVideoProxy extends ModelProxy
{
}

View File

@ -22,5 +22,6 @@ class ModuleServiceProvider extends CoreModuleServiceProvider
\Webkul\Product\Models\ProductBundleOptionTranslation::class,
\Webkul\Product\Models\ProductBundleOptionProduct::class,
\Webkul\Product\Models\ProductCustomerGroupPrice::class,
\Webkul\Product\Models\ProductVideo::class,
];
}

View File

@ -0,0 +1,60 @@
<?php
namespace Webkul\Product\Repositories;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Http\UploadedFile;
use Webkul\Core\Eloquent\Repository;
class ProductVideoRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\Product\Contracts\ProductVideo';
}
/**
* @param array $data
* @param \Webkul\Product\Contracts\Product $product
* @return void
*/
public function uploadVideos($data, $product)
{
$previousVideoIds = $product->videos()->pluck('id');
if (isset($data['videos'])) {
foreach ($data['videos'] as $videoId => $video) {
$file = 'videos.' . $videoId;
$dir = 'product/' . $product->id;
if ($video instanceof UploadedFile) {
if (request()->hasFile($file)) {
$this->create([
'path' => request()->file($file)->store($dir),
'product_id' => $product->id,
'type' => 'video'
]);
}
} else {
if (is_numeric($index = $previousVideoIds->search($videoId))) {
$previousVideoIds->forget($index);
}
}
}
}
foreach ($previousVideoIds as $videoId) {
if ($videoModel = $this->find($videoId)) {
Storage::delete($videoModel->path);
$this->delete($videoId);
}
}
}
}

View File

@ -11,6 +11,7 @@ use Webkul\Product\Repositories\ProductRepository;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Datatypes\CartItemValidationResult;
use Webkul\Product\Repositories\ProductImageRepository;
use Webkul\Product\Repositories\ProductVideoRepository;
use Webkul\Product\Repositories\ProductInventoryRepository;
use Webkul\Product\Repositories\ProductAttributeValueRepository;
@ -47,10 +48,17 @@ abstract class AbstractType
/**
* ProductImageRepository instance
*
* @var \Webkul\Product\Repositories\ProductInventoryRepository
* @var \Webkul\Product\Repositories\productImageRepository
*/
protected $productImageRepository;
/**
* ProductVideoRepository instance
*
* @var \Webkul\Product\Repositories\productVideoRepository
*/
protected $productVideoRepository;
/**
* Product Image helper instance
*
@ -140,6 +148,7 @@ abstract class AbstractType
* @param \Webkul\Product\Repositories\ProductAttributeValueRepository $attributeValueRepository
* @param \Webkul\Product\Repositories\ProductInventoryRepository $productInventoryRepository
* @param \Webkul\Product\Repositories\ProductImageRepository $productImageRepository
* @param \Webkul\Product\Repositories\ProductVideoRepository $productVideoRepository
* @param \Webkul\Product\Helpers\ProductImage $productImageHelper
*
* @return void
@ -150,6 +159,7 @@ abstract class AbstractType
ProductAttributeValueRepository $attributeValueRepository,
ProductInventoryRepository $productInventoryRepository,
ProductImageRepository $productImageRepository,
ProductVideoRepository $productVideoRepository,
ProductImage $productImageHelper
) {
$this->attributeRepository = $attributeRepository;
@ -162,6 +172,8 @@ abstract class AbstractType
$this->productImageRepository = $productImageRepository;
$this->productVideoRepository = $productVideoRepository;
$this->productImageHelper = $productImageHelper;
}
@ -271,6 +283,8 @@ abstract class AbstractType
$this->productImageRepository->uploadImages($data, $product);
$this->productVideoRepository->uploadVideos($data, $product);
app('Webkul\Product\Repositories\ProductCustomerGroupPriceRepository')->saveCustomerGroupPrices($data,
$product);
}

View File

@ -22,6 +22,7 @@ class Simple extends AbstractType
'admin::catalog.products.accordians.categories',
'admin::catalog.products.accordians.channels',
'admin::catalog.products.accordians.product-links',
'admin::catalog.products.accordians.videos',
];
/**

View File

@ -1,7 +1,15 @@
@inject ('productImageHelper', 'Webkul\Product\Helpers\ProductImage')
@inject ('productVideoHelper', 'Webkul\Product\Helpers\ProductVideo')
@inject ('wishListHelper', 'Webkul\Customer\Helpers\Wishlist')
<?php $images = $productImageHelper->getGalleryImages($product); ?>
<?php
$images = $productImageHelper->getGalleryImages($product);
$videos = $productVideoHelper->getVideos($product);
$images = array_merge($images, $videos);
?>
{!! view_render_event('bagisto.shop.products.view.gallery.before', ['product' => $product]) !!}
@ -30,7 +38,12 @@
</li>
<li class="thumb-frame" v-for='(thumb, index) in thumbs' @mouseover="changeImage(thumb)" :class="[thumb.large_image_url == currentLargeImageUrl ? 'active' : '']" id="thumb-frame">
<img :src="thumb.small_image_url" alt=""/>
<video v-if="thumb.type == 'video'" width="100" height="100" controls>
<source :src="thumb.video_url" type="video/mp4">
{{ __('admin::app.catalog.products.not-support-video') }}
</video>
<img v-else :src="thumb.small_image_url" alt=""/>
</li>
<li class="gallery-control bottom" @click="moveThumbs('bottom')" v-if="(thumbs.length > 4) && this.is_move.down">
@ -39,10 +52,13 @@
</li>
</ul>
<div class="product-hero-image" id="product-hero-image">
<img :src="currentLargeImageUrl" id="pro-img" :data-image="currentOriginalImageUrl" alt=""/>
<video :key="currentVideoUrl" v-if="currentType == 'video'" width="500" height="450" controls>
<source :src="currentVideoUrl" :data-image="currentOriginalImageUrl" type="video/mp4">
{{ __('admin::app.catalog.products.not-support-video') }}
</video>
<img v-else :src="currentLargeImageUrl" id="pro-img" :data-image="currentOriginalImageUrl" alt=""/>
@auth('customer')
@php
@ -76,6 +92,10 @@
currentOriginalImageUrl: '',
currentVideoUrl: '',
currentType: '',
counter: {
up: 0,
down: 0,
@ -114,11 +134,19 @@
},
changeImage: function(image) {
this.currentType = image.type;
if (image.type == 'video') {
this.currentVideoUrl = image.video_url;
this.currentLargeImageUrl = image.large_image_url = image.video_url;
} else {
this.currentLargeImageUrl = image.large_image_url;
this.currentOriginalImageUrl = image.original_image_url;
}
if ($(window).width() > 580) {
if ($(window).width() > 580 && image.original_image_url) {
$('img#pro-img').data('zoom-image', image.original_image_url).ezPlus();
}
},