'use strict' // const { urlencoded } = require('body-parser') const d = document , w = window let ProductTemplate = d.createElement( 'ProductTemplate' ) , ThumbnailsRowTemplate = d.createElement( 'ThumbnailsRowTemplate' ) , ThumbnailTemplate = d.createElement( 'ThumbnailTemplate' ) , ProductTypesDatalist = d.getElementById( 'product_types' ) , searchField = d.getElementById( 'search' ) , searchForm = d.querySelector( 'nav form' ) , btnShowAll = d.getElementById( 'ShowAll' ) , btnShowWithoutPhotos = d.getElementById( 'ShowWithoutPhotos' ) , chkShowOnlyAvaliable = d.getElementById( 'ShowOnlyAvaliable' ) , txtCurrentCount = d.getElementById( 'CurrentCount' ) , ProgressBarElement = d.querySelector( 'ProgressBarElement' ) , Main = d.getElementById( 'photos' ) , Moving_Thumbnail = null , unloadedProducts = [] , ProductTypes = new Set( ) , ProgressBar , ProgressBarTotal , MaxChunk = 100 , BaseURL = localStorage.getItem( 'BaseURL' ) || '' , Accounts_Ordered = JSON.parse( localStorage.getItem( 'Accounts_Ordered' ) || '[ ]' ) , Default_Accounts = JSON.parse( localStorage.getItem( 'Default_Accounts' ) || '{ }' ) , _HardReload_in_3_2_ = false , Products = Object.assign( { }, ...Object.keys( localStorage ).map( SKU => ( ![ 'lastUpdated', 'Accounts_Ordered', 'Default_Accounts', 'BaseURL' ].includes( SKU ) ) && ({ [ SKU ]: JSON.parse( localStorage.getItem( SKU ) || '{ }' ) }) || ({ }) ) ) //console.log( Products ) const ceil_abs = N => Math.ceil( Math.abs( N ) ) const range = ( cnt_or_start = 0, stop = null, step = 1 ) => ( null === stop ) && [...Array( cnt_or_start ).keys( )] || [...Array( ceil_abs( ( stop - cnt_or_start ) / step ) )] .map( ( _, i ) => cnt_or_start + i * step ) function PopulateSearchWithTypes( ) { [...ProductTypes] .sort( ( a,b ) => a.localeCompare( b ) ) .map( T => { let O = document.createElement( 'option' ) O.value = T ProductTypesDatalist.appendChild( O ) }) } const getHashFromPhotoURL = PhotoURL => PhotoURL.match( /([^\/_]+)_?L\.jpg$/ )[ 1 ] const unURIHash = ( Hash = w.location.hash ) => decodeURIComponent( Hash ) const Now = ( ) => new Date( ).toISOString( ) .slice( 0, 19 ) .replace( 'T', ' ' ) const Product_Error = { SKU: 'ERR-01-001' , Title: 'Cant get Products from server' , Variants: { _default: { 10:'', 1:'' } } , Accounts: { } } const DefaultRequestParams = { ContentType: 'application/json' , url: '/api/photos/' , method: 'GET' , body: '' } const Search_onKeypress = e => { if( ( typeof e.key == 'undefined' ) || ( e.key == null ) ) { return false } const regexValidKeys = new RegExp ( '^' + '[µßáäåæëíïðñóöøúüþœœ\\w\\d]' + '|Backspace|Delete|Clear|Cut|Paste|Undo|Redo' + '$' , 'i' ) if( regexValidKeys.test( e.key ) ) { showOnlySearched( ) } } const setURL = path => { let u = new URL( w.location.toString( ) ) u.pathname = '/photos/' + path u.hash = '' u.search = '' d.title = path history.pushState( { search: path }, path, u.toString( ) ) } const AddClassFor600ms = ( _DOMnode, _Class ) => { _DOMnode.classList.add( _Class ) setTimeout( ( ) => { _DOMnode.classList.remove( _Class ) } , 600 ) } const getPhotoURL = ({ SKU, Hash, Size }) => ( SKU && Hash && Size ) && ( BaseURL + GetID( SKU ) + '/' + Hash + ( Hash.length>2 ? '_' : '' ) + Size + '.jpg' ) || '' const getSearchIndex = ( ...args ) => [ ...args ] .join( '' ) .replace( /[^µßáäåæëíïðñóöøúüþœœ\w\d]/gi,'' ) .toUpperCase( ) const setProgressBarTotal = Total => { ProgressBarTotal = Total ProgressBar = -1 incrementProgressBar( ) } function incrementProgressBar( ) { ProgressBarElement.style.width = ( ++ProgressBar * 100 / ProgressBarTotal ) + '%' if( ProgressBar == ProgressBarTotal ) { Window_onScroll( ) } } function collapseAll( ) { setURL( searchField.value ) d.querySelectorAll( 'ProductElement.targeted' ) .forEach( P => P.classList.remove( 'targeted' ) ) } function unhideAll( ) { d.querySelectorAll( 'ProductElement.hidden' ) .forEach( P => P.classList.remove( 'hidden' ) ) } const UpdateCounter = Count => { CurrentCount.textContent = Count || d.querySelectorAll( 'ProductElement:not(.hidden):not(.zero)' ) .length } const ShowOnlyAvaliable = e => { const NonZero = Boolean( chkShowOnlyAvaliable.checked ) d.querySelectorAll( 'ProductElement' ) .forEach( P => { if( NonZero && Products[ P.dataset.sku ].Amount == 0 ) { P.classList.add('zero') } else { P.classList.remove('zero') } }) UpdateCounter( ) } const ScrollTo = P => { w.scroll( 0, P.offsetTop + P.offsetParent.offsetTop - 50 ) Window_onScroll( ) } const obj2URLSearchParams = obj => { let u = new URLSearchParams( ) for( const key in obj ) { u.set( key, obj[ key ] ) } return u.toString( ) } const request = Params => new Promise( ( resolve, reject ) => { let { url, method, body, ContentType } = DefaultRequestParams if( 'string' == typeof Params ) { url = Params } else { if( 'string' == typeof Params.url ) { url = Params.url } else { const { api, query } = Params let u = new URL( w.location.toString( ) ) u.pathname = '/api/photos/' + api u.search = obj2URLSearchParams( query || {} ) u.hash = '' url = u.toString( ) } method = Params.method || method body = Params.body || body ContentType = Params.ContentType || ContentType } const headers = Params.headers || { 'Content-Type' : ContentType } fetch( url , [ 'GET','HEAD' ].includes( method ) ? { headers, method } : { headers, method, body } ) .then( res => { if( !res || !res.ok ) reject( res && res.status || url ) if( res.statusCode >= 400 ) resolve( false ) resolve( res.text( ) ) }) .catch( err => { reject({ err, Params }) console.error( 'rejected request::fetch', err ) }) }).catch( err => console.error( 'cant [request]', err ) ) const GetID = SKU => SKU.replace( /\s+/g,'' ) .replace( /[^\w\d\.\,_-]/g,'-' ) .toUpperCase( ) const SendFiles = ({ Form, SKU, Variant, Nr }) => { let u = new URL( w.location.toString( ) ) u.pathname = '/api/photos' u.search = obj2URLSearchParams({ SKU, Variant, Nr: Nr||'0' }) Form.action = u.toString( ) Form.submit( ) } const ProcessPhotoFile = ({ PhotoFile, Nr }) => { let canvas = d.createElement( 'canvas' ) , ctx = canvas.getContext( '2d' ) , reader = new FileReader( ) , Hash return new Promise( ( resolve,reject ) => { reader.onload = e => { let img = new Image( ) img.onload = ( ) => { canvas.width = 200 canvas.height = 200 ctx.drawImage( img, 0, 0, 200, 200 ) let imageData = ctx.getImageData(0, 0, 200, 200) if( 10 == Nr ) { let data = imageData.data for( let i = 0; i < data.length; i += 4) { data[ i ] // red = data[ i+1 ] // green = data[ i+2 ] // blue = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2] } ctx.putImageData( imageData, 0, 0 ) } resolve({ imgData: canvas.toDataURL( 'image/jpeg' ) , imgWidth: img.naturalWidth , imgHeight: img.naturalHeight }) } img.src = e.target.result } try { reader.readAsDataURL( PhotoFile ) } catch( e ) { reject( e ) } } ).catch( err => console.error( 'cant [ProcessPhotoFile]', err )) } const UploadSinglePhoto = async e => { let Form = e.target.parentNode , T = Form.parentNode.parentNode , R = T.parentNode , P = R.parentNode.parentNode , PhotoFile = e.target.files[ 0 ] , SKU = P.dataset.sku , Nr = T.dataset.nr , Variant = R.dataset.variant , Hash = T.dataset.hash SendFiles({ Form, SKU, Variant, Nr }) T.dataset.src_big = getPhotoURL({ SKU, Hash, Size:'M' }) + '?' + Date.now( ) const { imgData, imgWidth, imgHeight } = await ProcessPhotoFile({ PhotoFile, Nr }) T.dataset.src_small = T.querySelector( 'img' ).src = imgData if( 1600 != imgWidth || 1600 != imgHeight ) { T.querySelector( 'WrongDimensions' ).innerText = imgWidth + 'x' + imgHeight } updateAccounts( P ) } const UploadMultiplePhotos = async e => { let Form = e.target.parentNode , R = Form.parentNode.parentNode.parentNode.parentNode , P = R.parentNode.parentNode , SKU = P.dataset.sku , Variant = R.dataset.variant , Photos = e.target.files , TT = R.querySelectorAll( 'Thumbnail' ) , Nr = TT.length-1 SendFiles({ Form, SKU, Variant }) for( const i in Photos ) { if( i != Number( i ) ) { updateAccounts( P ) return true } Nr = ( 9 == Nr ? 11 : Nr + 1 ) if( 10 < Nr ) { return false } const Hash = ''// await getFilehash( Photos[i] ) , PhotoFile = Photos[i] let T = createThumbnail( { SKU , Nr , Hash , Variant , PrevUUID: SKU + ':' + ( TT[ Nr-1 ] ? Nr-1 : TT.length-1 ) , NextUUID: SKU + ':' + ( Photos[ i+1 ] ? Nr+1 : 10 ) }) T.dataset.src_big = getPhotoURL({ SKU, Hash, Size:'M' }) const { imgData, imgWidth, imgHeight } = await ProcessPhotoFile({ PhotoFile, Nr }) T.dataset.src_small = T.querySelector( 'img' ).src = imgData if( 1600 != imgWidth || 1600 != imgHeight ) { T.querySelector( 'WrongDimensions' ).innerText = imgWidth + 'x' + imgHeight } R.appendChild( T ) } } const VariantSelector_onChange = e => { let S = e.target || e , R = S.parentNode.parentNode , P = R.parentNode.parentNode const SKU = P.dataset.sku , Account = R.dataset.account , Variant = S.value , Variants = Object.keys( Products[ SKU ].Variants ) Products[ SKU ].Accounts[ Account ] = Variant request({ api: 'assignVariantToAccount' , method: 'PUT' , query: { SKU, Account, Variant } }) R.parentNode .replaceChild( createAccount( { SKU , Account , Variant , Variants , ProductElement: P } ) , R ) } const updateAccounts = ProductElement => { ProductElement.querySelectorAll( 'Variants ThumbnailsRow' ).forEach( V => { ProductElement.querySelectorAll( 'Accounts ThumbnailsRow' ).forEach( A => { if( V.dataset.variant != A.dataset.variant ) { return false } A.querySelector( 'select' ).value = A.dataset.variant A.querySelectorAll( 'Thumbnail' ).forEach( T => A.removeChild( T ) ) V.querySelectorAll( 'Thumbnail' ).forEach( T => { let newT = A.appendChild( T.cloneNode( true )) AddClassFor600ms( newT, 'updated' ) }) }) }) Window_onScroll( ) } const openNewVariantDialog = e => { let NewVariantDialog = e.target.parentNode.parentNode.parentNode NewVariantDialog.querySelector( 'ConfirmationDialog' ) .classList.toggle( 'hidden' ) NewVariantDialog.querySelector( 'button' ) .classList.toggle( 'hidden' ) let VariantName = NewVariantDialog.querySelector( `input[name='variant_name']` ) VariantName.value = Now( ) VariantName.select( ) VariantName.focus( ) } const abortNewVariant = e => { let NewVariantDialog = e.target.parentNode.parentNode.parentNode NewVariantDialog.querySelector( 'ConfirmationDialog' ) .classList.toggle( 'hidden' ) NewVariantDialog.querySelector( 'button' ) .classList.toggle( 'hidden' ) } const confirmNewVariant = e => { let NewVariantDialog = e.target && e.target.parentNode.parentNode.parentNode || e.parentNode.parentNode , P = NewVariantDialog.parentNode.parentNode , VariantName = NewVariantDialog.querySelector( `input[name='variant_name']` ) NewVariantDialog.querySelector( 'ConfirmationDialog' ) .classList.toggle( 'hidden' ) NewVariantDialog.querySelector( 'button' ) .classList.toggle( 'hidden' ) if( !VariantName.value.trim( ) ) { VariantName.value = Now( ) } const SKU = P.dataset.sku , Variant = VariantName.value Products[ SKU ].Variants[ Variant ] = { } Products[ SKU ].Variants[ Variant ][ 10 ] = Products[ SKU ].Variants[ '_default' ][ 10 ] || '' let V = P.querySelector( `Variants [data-variant='${Variant}']` ) if( V ) { ScrollTo( V ) return false } request({ api: 'variant' , method: 'POST' , query: { SKU, Variant } }) let newV = createVariant( { SKU , Variant , Photos: Products[ SKU ].Variants[ Variant ] } ) newV.querySelectorAll( 'Thumbnail' ).forEach( T => { AddClassFor600ms( T, 'updated' ) } ) ScrollTo( P.querySelector( 'Variants' ).appendChild( newV )) P.querySelectorAll( 'Accounts select' ).forEach( S => { let O = d.createElement( 'option' ) O.textContent = Variant S.appendChild( O ) } ) } const copyVariant = e => { const R = e.target && e.target.parentNode.parentNode || e.parentNode.parentNode , P = R.parentNode.parentNode , SKU = P.dataset.sku , CopyOf = R.dataset.variant , Variant = 'Copy ' + Now( ) console.log({R,P, SKU, CopyOf, Variant}) Products[ SKU ].Variants[ Variant ] = Products[ SKU ].Variants[ CopyOf ] request({ api: 'variant' , method: 'POST' , query: { SKU, Variant, CopyOf } }) let newV = createVariant( { SKU , Variant , Photos: Products[ SKU ].Variants[ Variant ] }) newV.querySelectorAll( 'Thumbnail' ).forEach( T => { AddClassFor600ms( T, 'updated' ) } ) ScrollTo( P.querySelector( 'Variants' ).appendChild( newV )) P.querySelectorAll( 'Accounts select' ).forEach( S => { let O = d.createElement( 'option' ) O.textContent = Variant S.appendChild( O ) }) } const toggleProduct = e => { e.preventDefault( ) let P = e.target.parentNode.parentNode.parentNode , SKU = P.dataset.sku if( P.classList.contains( 'targeted' ) ) { collapseAll( ) P.querySelectorAll( 'Thumbnail' ) .forEach( T => T.classList.remove( 'updated' ) ) } else { expandProduct( P ) } return true } const expandProduct = P => { unzoomAll( ) P.classList.remove( 'hidden' ) P.classList.add( 'targeted' ) ScrollTo( P ) const SKU = P.dataset.sku setURL( '!'+SKU ) if( P.querySelector( 'Accounts ThumbnailsRow' ) ) { return true } let Product = Products[ SKU ] || { } , Variants = [] , NewVariant = P.querySelector( 'NewVariant' ) , ConfirmNewVariant = NewVariant.querySelector( 'ConfirmNewVariant' ) , AbortNewVariant = NewVariant.querySelector( 'AbortNewVariant' ) , NewVariantName = NewVariant.querySelector( 'input[type=text]' ) NewVariant.querySelector( 'button' ).onclick = openNewVariantDialog ConfirmNewVariant.onclick = confirmNewVariant AbortNewVariant.onclick = abortNewVariant NewVariantName.onkeypress = e => { if( e.key == 'Escape' ) { AbortNewVariant.click( ) return false } else if( e.key == 'Enter' ) { confirmNewVariant( ConfirmNewVariant ) } } if( !Product.Variants ) { Product.Variants = { _default: { } } } for( const Variant in Product.Variants ) { Variants.push( Variant ) if( Variant != '_default' ) { P.querySelector( 'Variants' ).appendChild( createVariant( { SKU , Variant , Photos: Product.Variants[Variant] }) ) } } if( !Object.keys( Product.Accounts ).length ) { Products[ SKU ].Accounts = Product.Accounts = Default_Accounts } Accounts_Ordered.map( Account => P.querySelector( 'Accounts' ).appendChild ( createAccount( { Account , Variant: Product.Accounts[ Account ] || '_default' , Variants , ProductElement: P , SKU }) ) ) ScrollTo( P ) } const Window_onScroll = e => { const Window_Height = w.innerHeight || d.documentElement.clientHeight d.querySelectorAll( 'ThumbnailsRow' ) .forEach( ( R, i, a ) => { let Rect = R.getBoundingClientRect( ) if( ( 0 <= ( Rect.top + Rect.height ) ) && ( Rect.top <= Window_Height ) ) { // if( ( i == a.length-1 ) // && ( !d.querySelector( '.targeted' ) ) // ) // { showProducts( ) } if( ( R.querySelector( 'Thumbnail img' ).src == R.querySelector( 'Thumbnail' ).dataset.src_small ) || ( R.querySelector( 'Thumbnail img' ).src == R.querySelector( 'Thumbnail' ).dataset.src_big ) ) { return false } R.querySelectorAll( 'Thumbnail:not(.zoomed)' ) .forEach( T => T.querySelector( 'img' ).src = T.dataset.src_small ) } }) } const Window_onResize = e => { d.getElementById( 'ZoomedStyle' ).innerText = `.zoomed { top: ${ w.innerHeight/2 - 400 }px !important ; left: ${ w.innerWidth/2 - 400 }px !important ; position: fixed } `.replace( /\s\s+/g, '\n' ) Window_onScroll( ) } const Window_onKeyDown = e => { let Zoomed = d.querySelector( 'Thumbnail.zoomed' ) if( Zoomed ) { if( e.key == 'Escape' ) { unzoomAll( ) return true } if( /^(k|n|ArrowRight)$/i.test( e.key )) Zoomed.querySelector( 'Next' ).click( ) if( /^(j|p|ArrowLeft)$/i.test( e.key )) Zoomed.querySelector( 'Prev' ).click( ) } else { if( e.key == 'Escape' ) { collapseAll( ) unhideAll( ) showProducts( ) } if( ( e.key == 'R' && ( e.ctrlKey || e.metaKey ) ) || ( ( e.which || e.keyCode ) == 116 ) ) { console.log( 'Hard Reload in 3.. 2.. ' ) _HardReload_in_3_2_ = true } } } const openThumbnailRemovalDialog = e => { unzoomAll( ) let RemovalButton = e.target , ConfirmationDialog = RemovalButton.querySelector( 'ConfirmationDialog' ) , Icon = RemovalButton.querySelector( 'Icon' ) if( !( ConfirmationDialog && Icon )) { return false } ConfirmationDialog.classList.remove( 'hidden' ) Icon.classList.add( 'hidden' ) // RemovalButton.removeEventListener( 'click', openThumbnailRemovalDialog ) } const abortThumbnailRemoval = e => { let RemovalDialog = e.target.parentNode.parentNode.parentNode RemovalDialog.querySelector( 'Icon' ).classList.remove( 'hidden' ) RemovalDialog.querySelector( 'ConfirmationDialog' ).classList.add( 'hidden' ) // RemovalDialog.addEventListener( 'click', openThumbnailRemovalDialog ) } const confirmThumbnailRemoval = e => { unzoomAll( ) let RemoveButton = e.target.parentNode.parentNode , T = RemoveButton.parentNode , R = T.parentNode , P = R.parentNode.parentNode const SKU = P.dataset.sku , Variant = R.dataset.variant , Nr = T.dataset.nr , Hash = T.dataset.hash console.log( 'removing photo:', SKU, Variant, Nr, Hash ) request({ api: '' , method: 'DELETE' , query: { SKU, Variant, Nr } }) if( 10 == Nr ) { delete Products[ SKU ].Variants[ Variant ][ 10 ] } else { const newAmountOfRegularPhotos = Object .keys( Products[ SKU ].Variants[ Variant ] ) .filter( k => 10 > k ) .length - 1 for( let _Nr = Nr; _Nr <= 9; _Nr++ ) { if( _Nr <= newAmountOfRegularPhotos ) { Products[ SKU ].Variants[ Variant ][ _Nr ] = Products[ SKU ].Variants[ Variant ][ Number( _Nr ) + 1 ] } else { delete Products[ SKU ].Variants[ Variant ][ _Nr ] } } } let V = createVariant( { SKU , Variant , Photos: Products[ SKU ].Variants[ Variant ] }) R.parentNode.replaceChild( V, R ) AddClassFor600ms( V, 'changed_numeration' ) updateAccounts( P ) } const openVariantRemovalDialog = e => { unzoomAll( ) let RemovalButton = e.target , ConfirmationDialog = RemovalButton.querySelector( 'ConfirmationDialog' ) , Icon = RemovalButton.querySelector( 'Icon' ) if( !( ConfirmationDialog && Icon )) { return false } ConfirmationDialog.classList.remove( 'hidden' ) Icon.classList.add( 'hidden' ) // RemovalButton.removeEventListener( 'click', openVariantRemovalDialog ) } const abortVariantRemoval = e => { let RemovalDialog = e.target.parentNode.parentNode RemovalDialog.querySelector( 'icon' ).classList.remove( 'hidden' ) RemovalDialog.querySelector( 'ConfirmationDialog' ).classList.add( 'hidden' ) // RemovalDialog.addEventListener( 'click', openVariantRemovalDialog ) } const confirmVariantRemoval = e => { const R = e.target.parentNode.parentNode.parentNode.parentNode , P = R.parentNode.parentNode , Variant = R.dataset.variant , SKU = P.dataset.sku console.log( 'removing', SKU, Variant ) request({ api: 'variant' , method: 'DELETE' , query: { SKU, Variant } }) if( Products[ SKU ].Variants[ Variant ] ) { delete Products[ SKU ].Variants[ Variant ] localStorage.setItem( SKU, JSON.stringify( Products[ SKU ] ) ) } // if( Variant == '_default' ) // let chkNewDefault = P.querySelector('input[type=checkbox]:not(:checked)') // if( chkNewDefault ) // changeDefaultVariant( chkNewDefault ) // } // } R.parentNode.removeChild( R ) } const unzoomAll = ( ) => { d.querySelectorAll( 'Thumbnail.zoomed' ).forEach( z => { //console.log(z) z.querySelector( 'Prev' ).classList.add( 'hidden' ) z.querySelector( 'Next' ).classList.add( 'hidden' ) z.querySelector( 'Zoom i' ).classList.add( 'fa-search-plus' ) z.querySelector( 'Zoom i' ).classList.remove( 'fa-search-minus' ) z.classList.remove( 'zoomed' ) z.querySelector( 'img' ).src = z.dataset.src_small }) } const ThumbnailZoomToggle = T => { if( T.classList.contains( 'zoomed' ) ) { unzoomAll( ) } else { ThumbnailZoomIn( T ) } } const ThumbnailZoomIn = T => { unzoomAll( ) T.querySelector( 'Prev' ).classList.remove( 'hidden' ) T.querySelector( 'Next' ).classList.remove( 'hidden' ) T.querySelector( 'Zoom i' ).classList.remove( 'fa-search-plus' ) T.querySelector( 'Zoom i' ).classList.add( 'fa-search-minus' ) T.classList.add( 'zoomed' ) T.querySelector( 'img' ).src = T.dataset.src_big } const Moving_inProgress = Pos => { Moving_Thumbnail.style.top = Pos.pageY+10 + 'px' Moving_Thumbnail.style.left = Pos.pageX+5 + 'px' Pos.preventDefault( ) } const Moving_Stop = e => { e.preventDefault( ) d.removeEventListener( 'mousemove', Moving_inProgress ) d.removeEventListener( 'mouseup', Moving_Stop ) if( Moving_Thumbnail != d.querySelector( 'Thumbnail.moving' ) ) { return false } let R = Moving_Thumbnail.parentNode , P = R.parentNode.parentNode , SKU = P.dataset.sku , Variant = R.dataset.variant , OldNr = Moving_Thumbnail.dataset.nr R.querySelectorAll( 'MovePlaceholder' ) .forEach( p => p.classList.add( 'hidden' ) ) R.classList.remove( 'has_moving' ) Moving_Thumbnail.classList.remove( 'moving' ) Moving_Thumbnail.style = '' let Place = d.querySelector( 'Thumbnail MovePlaceholder:hover' ) if( !Place || ( Place.parentNode.dataset.uuid == Moving_Thumbnail.dataset.prevuuid ) ) { return false } // rearrange photos let NewNr = Number( Place.textContent ) console.log( 'reordering photos', OldNr, '>>', NewNr ) Place.parentNode.insertAdjacentElement( 'afterend', Moving_Thumbnail ) AddClassFor600ms( Moving_Thumbnail.querySelector( 'img' ), 'updated' ) R.querySelectorAll( 'Thumbnail' ) .forEach( ( T, i, a ) => { const NrBefore = T.dataset.nr , NrAfter = ( i<10 ? ( i==0 ? 10 : i ) : i+1 ) T.dataset.nr = T.querySelector( 'ThumbnailCaption' ).textContent = NrAfter T.dataset.uuid = T.title = T.dataset.sku + ':' + NrAfter T.dataset.prevuuid = T.dataset.sku + ':' + ( [ a.length, 10, ...range( 1,10 ) ][i] || i ) T.dataset.nextuuid = T.dataset.sku + ':' + ( i==a.length-1 ? 10 : ( i<9 ? i+1 : i+2 ) ) }) AddClassFor600ms( R, 'changed_numeration' ) request({ api: 'renumber' , method: 'PUT' , query: { SKU, Variant, OldNr, NewNr } }) updateAccounts( P ) Moving_Thumbnail = null } const Move = e => { if( e.button == 0 ) { Moving_Thumbnail = e.target.parentNode Moving_Thumbnail.classList.add( 'moving' ) Moving_Thumbnail.parentNode.classList.add( 'has_moving' ) let R = Moving_Thumbnail.parentNode R.querySelectorAll( 'Thumbnail:not( .moving ) MovePlaceholder' ) .forEach( ( MovePlaceholder, i, a ) => { MovePlaceholder.textContent = i+1 MovePlaceholder.classList.remove( 'hidden' ) } ) Moving_inProgress( e ) d.addEventListener( 'mousemove', Moving_inProgress ) d.addEventListener( 'mouseup', Moving_Stop ) e.preventDefault( ) } } const Prev = e => { const T = e.target.parentNode , R = T.parentNode , PrevT = R.querySelector( `[data-uuid='${T.dataset.prevuuid}']` ) if( PrevT ) { ThumbnailZoomIn( PrevT ) } } const Next = e => { const T = e.target.parentNode , R = T.parentNode , NextT = R.querySelector( `[data-uuid='${T.dataset.nextuuid}']` ) if( NextT ) { ThumbnailZoomIn( NextT ) } } const createThumbnail = ({ SKU, Nr, Hash, PrevUUID, NextUUID, Variant }) => { let T = ThumbnailTemplate .querySelector( 'Thumbnail' ) .cloneNode( true ) Hash = Hash || '' T.dataset.hash = Hash T.dataset.src_big = getPhotoURL({ SKU, Hash, Nr, Size:'M' }) T.dataset.src_small = getPhotoURL({ SKU, Hash, Nr, Size:'S' }) || '/placeholder.svg' T.querySelector( 'img' ).src = '/placeholder.svg' T.querySelector( 'ThumbnailCaption' ).textContent = Nr T.querySelector( 'Upload input' ).onchange = UploadSinglePhoto T.querySelector( 'Remove' ).addEventListener( 'click', openThumbnailRemovalDialog ) T.querySelector( 'Remove AbortRemoval' ).onclick = abortThumbnailRemoval T.querySelector( 'Remove ConfirmRemoval' ).onclick = confirmThumbnailRemoval T.querySelector( 'Move' ).onmousedown = Move T.querySelector( 'Zoom' ).onclick = e => ThumbnailZoomToggle( e.target.parentNode ) T.querySelector( 'Prev' ).onclick = Prev T.querySelector( 'Next' ).onclick = Next T.dataset.prevuuid = PrevUUID T.dataset.nextuuid = NextUUID T.dataset.sku = SKU T.dataset.nr = Nr T.dataset.uuid = T.title = SKU+':'+Nr return T } const changeDefaultVariant = e => { const newDefaultCheckbox = e.target || e , R = newDefaultCheckbox.parentNode.parentNode , P = R.parentNode.parentNode , SKU = P.dataset.sku , NewDefaultVariant = R.dataset.variant , oldDefaultRow = P.querySelector( `Variants ThumbnailsRow[data-variant='_default']` ) , oldDefaultCheckbox = oldDefaultRow.querySelector( `input[type='checkbox']` ) , oldVarinatRenamedTo = Now( ) oldDefaultRow.dataset.variant = oldVarinatRenamedTo oldDefaultCheckbox.checked = oldDefaultCheckbox.disabled = false newDefaultCheckbox.checked = newDefaultCheckbox.disabled = true oldDefaultRow.querySelector('RowTitle').textContent = oldVarinatRenamedTo Products[ SKU ].Variants[ oldVarinatRenamedTo ] = Products[ SKU ].Variants[ '_default' ] R.dataset.variant = '_default' R.querySelector('RowTitle').textContent = '' Products[ SKU ].Variants[ '_default' ] = Products[ SKU ].Variants[ NewDefaultVariant ] delete Products[ SKU ].Variants[ NewDefaultVariant ] updateAccounts( P ) request({ api: 'setDefaultVariant' , method: 'PUT' , query: { SKU, Variant: NewDefaultVariant, oldVarinatRenamedTo } }) AddClassFor600ms( R, 'updated' ) console.log( `set '${NewDefaultVariant}' as default of '${SKU}',` , 'old default renamed to', oldVarinatRenamedTo ) } const createVariant = ({ SKU, Variant, Photos }) => { let R = ThumbnailsRowTemplate .querySelector( 'ThumbnailsRow' ) .cloneNode( true ) let isDefault = R.querySelector( 'RowDescription input[type=checkbox]' ) isDefault.onchange = changeDefaultVariant isDefault.checked = ( Variant == '_default' ) isDefault.disabled = isDefault.checked R.querySelector( 'RowTitle' ).textContent = Variant.replace( '_default', '' ) R.dataset.variant = Variant R.querySelector( 'input[type=file]' ).onchange = UploadMultiplePhotos R.querySelector( 'RowDescription Remove' ).addEventListener( 'click', openVariantRemovalDialog ) R.querySelector( 'RowDescription Remove AbortRemoval' ).onclick = abortVariantRemoval R.querySelector( 'RowDescription Remove ConfirmRemoval' ).onclick = confirmVariantRemoval R.querySelector( 'RowDescription Copy' ).onclick = copyVariant const Photo_Set = [ ...( new Set([ '10', ...Object.keys( Photos ).sort( ) ]) ) ] Photo_Set.forEach( ( Nr, i, a ) => { R.appendChild( createThumbnail( { SKU , Nr , Hash: Photos[Nr] || '' , Variant , PrevUUID: SKU + ':' + ( a[i-1] ? a[i-1] : a[a.length-1] ) , NextUUID: SKU + ':' + ( a[i+1] ? a[i+1] : a[0] ) } ) ) }) return R } const createAccount = ({ Account, Variant, Variants, ProductElement, SKU }) => { const VariantRowSelector = `Variants ThumbnailsRow[data-variant='${Variant}']` , DefaultRowSelector = `Variants ThumbnailsRow` let R = ( ProductElement.querySelector( VariantRowSelector ) || ProductElement.querySelector( DefaultRowSelector ) ).cloneNode( true ) R.querySelector( 'RowDescription input[type=checkbox]' ) .classList.add( 'hidden' ) R.dataset.account = R.querySelector( 'RowTitle' ).textContent = Account let S = R.querySelector( `select[name='VariantSelector']` ) S.onchange = VariantSelector_onChange Variants.forEach( V => { let O = d.createElement( 'option' ) O.textContent = V O.selected = ( V == Variant ) S.appendChild( O ) } ) return R } const createProduct = Product => { if( d.querySelector( `ProductElement[id='${Product.SKU}']` ) ) { return false } let P = ProductTemplate.querySelector( 'ProductElement' ) .cloneNode( true ) P.dataset.searchindex = getSearchIndex( Product.SKU, Product.Title, Product.Type ) P.querySelector( 'ProductAmount' ).textContent = Product.Amount P.querySelector( 'ProductSKU a' ).onclick = toggleProduct P.querySelector( 'ProductSKU a' ).href = '/photos/!'+Product.SKU P.querySelector( 'ProductSKU a' ).textContent = P.dataset.sku = P.id = Product.SKU P.querySelector( 'ProductTitle' ).innerHTML = Product.Title.replace( /\[/, '