2020-10-05 00:30:28 +03:00
import * as React from "react" ;
import * as Constants from "~/common/constants" ;
import * as SVG from "~/common/svg" ;
import * as Strings from "~/common/strings" ;
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview" ;
import { CheckBox } from "~/components/system/components/CheckBox" ;
import { css } from "@emotion/react" ;
import { LoaderSpinner } from "~/components/system/components/Loaders" ;
import { Toggle } from "~/components/system/components/Toggle" ;
import { Boundary } from "~/components/system/components/fragments/Boundary" ;
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation" ;
import { dispatchCustomEvent } from "~/common/custom-events" ;
import { DynamicIcon } from "~/components/core/DynamicIcon" ;
import { Tooltip } from "~/components/core/Tooltip" ;
import {
ButtonPrimary ,
ButtonSecondary ,
ButtonDisabled ,
ButtonWarning ,
} from "~/components/system/components/Buttons" ;
//NOTE(martina): sets 200px as the standard width for a 1080px wide layout with 20px margin btwn images.
//If the container is larger or smaller, it scales accordingly by that factor
const MIN _SIZE = 10 ;
const SIZE = 200 ;
const MARGIN = 20 ;
const CONTAINER _SIZE = 5 * SIZE + 4 * MARGIN ;
const TAG _HEIGHT = 20 ;
const SIZE _LIMIT = 1000000 ; //NOTE(martina): 1mb limit for twitter preview images
const generateLayout = ( items ) => {
if ( ! items ) {
return [ ] ;
}
if ( ! items . length ) {
return [ ] ;
}
return (
items . map ( ( item , i ) => {
return {
x : ( i % 5 ) * ( SIZE + MARGIN ) ,
y : 0 ,
w : SIZE ,
h : null ,
z : 0 ,
id : item . id ,
} ;
} ) || [ ]
) ;
} ;
const preload = ( item ) =>
new Promise ( ( resolve , reject ) => {
if ( ! item . type || ! item . type . startsWith ( "image/" ) ) {
resolve ( 200 ) ;
}
const img = new Image ( ) ;
img . onload = ( ) => {
resolve ( ( 200 * img . height ) / img . width ) ;
} ;
img . onerror = reject ;
const url = item . url . replace ( "https://undefined" , "https://" ) ;
img . src = url ;
} ) ;
const STYLES _MOBILE _HIDDEN = css `
@ media ( max - width : $ { Constants . sizes . mobile } px ) {
display : none ;
}
` ;
const STYLES _COPY _INPUT = css `
pointer - events : none ;
position : absolute ;
opacity : 0 ;
` ;
const STYLES _LOADER = css `
display : flex ;
align - items : center ;
justify - content : center ;
height : calc ( 100 vh - 400 px ) ;
width : 100 % ;
` ;
const STYLES _EDIT _CONTAINER = css `
border : 1 px solid rgba ( 229 , 229 , 229 , 0.5 ) ;
padding : 24 px ;
overflow : hidden ;
` ;
const STYLES _CONTAINER = css `
width : 100 % ;
position : relative ;
height : 100 vh ;
z - index : $ { Constants . zindex . body } ;
2020-10-22 08:38:37 +03:00
overflow : hidden ;
2020-10-05 00:30:28 +03:00
` ;
const STYLES _CONTAINER _EDITING = css `
$ { STYLES _CONTAINER }
background - image : radial - gradient (
2020-10-22 08:38:37 +03:00
$ { Constants . system . darkGray } 10 % ,
2020-10-05 00:30:28 +03:00
transparent 0
) ;
background - size : 30 px 30 px ;
background - position : - 50 % - 50 % ;
` ;
const STYLES _BUTTONS _ROW = css `
display : flex ;
flex - direction : row ;
align - items : center ;
` ;
const STYLES _TOGGLE _BOX = css `
$ { STYLES _BUTTONS _ROW }
border : 1 px solid $ { Constants . system . gray } ;
border - radius : 4 px ;
height : 40 px ;
padding : 0 16 px ;
` ;
const STYLES _ITEM = css `
position : absolute ;
transform - origin : top left ;
cursor : pointer ;
- webkit - touch - callout : none ; /* iOS Safari */
- webkit - user - select : none ; /* Safari */
- khtml - user - select : none ; /* Konqueror HTML */
- moz - user - select : none ; /* Old versions of Firefox */
- ms - user - select : none ; /* Internet Explorer/Edge */
user - select : none ; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */
` ;
const STYLES _ITEM _EDITING = css `
$ { STYLES _ITEM }
cursor : grab ;
: active {
cursor : grabbing ;
}
` ;
const STYLES _FILE _TAG = css `
2020-10-22 08:38:37 +03:00
font - family : $ { Constants . font . text } ;
color : $ { Constants . system . grayBlack } ;
2020-10-05 00:30:28 +03:00
display : flex ;
2020-10-22 08:38:37 +03:00
align - items : center ;
2020-10-05 00:30:28 +03:00
width : 100 % ;
2020-10-22 08:38:37 +03:00
padding : 0 4 px ;
2020-10-05 00:30:28 +03:00
background : $ { Constants . system . white } ;
` ;
const STYLES _FILE _NAME = css `
width : 100 % ;
min - width : 10 % ;
overflow : hidden ;
text - wrap : nowrap ;
white - space : nowrap ;
text - overflow : ellipsis ;
text - align : left ;
` ;
const STYLES _FILE _TYPE = css `
2020-10-22 08:38:37 +03:00
color : $ { Constants . system . darkGray } ;
2020-10-05 00:30:28 +03:00
text - transform : uppercase ;
flex - shrink : 0 ;
margin - left : 16 px ;
text - align : right ;
` ;
const STYLES _HANDLE _BOX = css `
2020-10-22 01:54:01 +03:00
cursor : nwse - resize ;
2020-10-05 00:30:28 +03:00
position : absolute ;
2020-10-22 01:54:01 +03:00
height : 24 px ;
width : 24 px ;
color : rgba ( 195 , 195 , 196 , 0.75 ) ;
2020-10-05 00:30:28 +03:00
` ;
const STYLES _ACTION _BAR = css `
display : flex ;
align - items : center ;
justify - content : space - between ;
box - shadow : 0 0 0 1 px $ { Constants . system . lightBorder } inset ,
0 0 4 px 2 px $ { Constants . system . shadow } ;
border - radius : 4 px ;
padding : 12 px 32 px ;
box - sizing : border - box ;
background - color : $ { Constants . system . foreground } ;
position : fixed ;
bottom : 12 px ;
width : calc ( 100 vw - $ { Constants . sizes . sidebar } px + 32 px ) ;
max - width : 1660 px ;
z - index : $ { Constants . zindex . header } ;
@ media ( max - width : $ { Constants . sizes . mobile } px ) {
width : calc ( 100 vw - 48 px ) ;
}
` ;
const STYLES _RIGHT = css `
flex - shrink : 0 ;
display : flex ;
align - items : center ;
` ;
const STYLES _LEFT = css `
width : 100 % ;
min - width : 10 % ;
display : flex ;
align - items : center ;
` ;
const STYLES _FILES _SELECTED = css `
font - family : $ { Constants . font . semiBold } ;
@ media ( max - width : $ { Constants . sizes . mobile } px ) {
display : none ;
}
` ;
const STYLES _ICON _BOX = css `
height : 32 px ;
width : 32 px ;
display : inline - flex ;
align - items : center ;
justify - content : center ;
cursor : pointer ;
margin - left : 16 px ;
` ;
const STYLES _ICON _CIRCLE = css `
height : 24 px ;
width : 24 px ;
border - radius : 50 % ;
background - color : rgba ( 248 , 248 , 248 , 0.6 ) ;
color : # 4 b4a4d ;
display : flex ;
align - items : center ;
justify - content : center ;
cursor : pointer ;
margin : 0 8 px ;
- webkit - backdrop - filter : blur ( 25 px ) ;
backdrop - filter : blur ( 25 px ) ;
` ;
const STYLES _ICON _ROW = css `
display : flex ;
flex - direction : row ;
position : absolute ;
left : calc ( 50 % - 60 px ) ;
` ;
export class SlateLayout extends React . Component {
_ref ;
_input ;
keysPressed = { } ;
state = {
unit : 10 ,
items : this . props . items ,
layout : this . props . layout || generateLayout ( this . props . items ) ,
hover : null ,
containerHeight : 1000 ,
prevLayouts : [ ] ,
zIndexMax :
this . props . layout && this . props . layout . length
? Math . max ( ... this . props . layout . map ( ( pos ) => pos . z ) ) + 1
: 1 ,
fileNames : this . props . fileNames ,
defaultLayout : this . props . layout ? this . props . defaultLayout : true ,
editing : false ,
show : false ,
checked : { } ,
copyValue : "" ,
tooltip : null ,
} ;
//LEFT OFF HERE:
//make sure things work if you delete while in editing mode (haven't tested that yet)
//add the "delete" and checkbox actions. drag handle. and make preview image
//if there's repeats of ids (not cids), it'll style them the same way (potentially diff position though) everytime you change something
//if width under a certain amount, set to a default mobile flex or grid layout of single items
//add flag for following defaultLayout for last element or not. so know where to put next one. "unchanged since last add" flag. if something deleted, messes this up as well
componentDidMount = async ( ) => {
2020-10-22 01:54:01 +03:00
this . debounceInstance = this . debounce ( this . _recalculate , 250 ) ;
window . addEventListener ( "resize" , this . debounceInstance ) ;
window . addEventListener ( "remote-object-update" , this . _handleRemoteEditObject ) ;
2020-10-05 00:30:28 +03:00
await this . calculateUnit ( ) ;
if ( this . props . layout ) {
let layout = await this . repairLayout ( this . state . items ) ;
if ( layout ) {
this . setState ( { show : true , layout } ) ;
this . props . onSaveLayout (
{
ver : "2.0" ,
fileNames : this . state . fileNames ,
defaultLayout : this . state . defaultLayout ,
layout ,
} ,
true
) ;
} else {
this . setState ( { show : true } ) ;
}
} else {
let layout = await this . calculateLayout ( ) ;
this . props . onSaveLayout (
{
ver : "2.0" ,
fileNames : this . state . fileNames ,
defaultLayout : this . state . defaultLayout ,
layout ,
} ,
true
) ;
this . setState ( { show : true , layout } ) ;
}
this . calculateContainer ( ) ;
} ;
componentWillUnmount = ( ) => {
2020-10-22 01:54:01 +03:00
window . removeEventListener ( "resize" , this . debounceInstance ) ;
window . removeEventListener ( "remote-object-update" , this . _handleRemoteEditObject ) ;
2020-10-05 00:30:28 +03:00
if ( this . state . editing ) {
window . removeEventListener ( "keydown" , this . _handleKeyDown ) ;
window . removeEventListener ( "keyup" , this . _handleKeyUp ) ;
}
} ;
componentDidUpdate = async ( prevProps ) => {
if ( prevProps . slateId !== this . props . slateId ) {
//NOTE(martina): to handle when you navigate between two slates, so it registers the change properly
await this . setState ( { show : false } ) ;
let defaultLayout = this . props . layout ? this . props . defaultLayout : true ;
let fileNames = this . props . fileNames ;
let layout ;
if ( this . props . layout ) {
layout = await this . repairLayout ( this . props . items , {
defaultLayout ,
fileNames ,
layout : this . props . layout ,
} ) ;
if ( layout ) {
this . props . onSaveLayout (
{
ver : "2.0" ,
fileNames ,
defaultLayout ,
layout ,
} ,
true
) ;
} else {
layout = this . props . layout ;
}
} else {
layout = generateLayout ( this . props . items ) ;
await this . setState ( { layout , items : this . props . items } ) ;
layout = await this . calculateLayout ( layout ) ;
this . props . onSaveLayout (
{
ver : "2.0" ,
fileNames ,
defaultLayout ,
layout ,
} ,
true
) ;
}
await this . setState ( {
items : this . props . items ,
layout ,
prevLayouts : [ ] ,
2020-10-22 01:54:01 +03:00
zIndexMax : layout && layout . length ? Math . max ( ... layout . map ( ( pos ) => pos . z ) ) + 1 : 1 ,
2020-10-05 00:30:28 +03:00
fileNames ,
defaultLayout ,
editing : false ,
show : true ,
} ) ;
this . calculateContainer ( ) ;
} else if ( prevProps . items . length !== this . props . items . length ) {
//NOTE(martina): to handle when items are added / deleted from the slate, and recalculate the layout
//NOTE(martina): if there is a case that allows simultaneous add / delete (aka modify but same length), this will not work.
//would need to replace it with event listener + custom events
let layout = await this . repairLayout ( this . props . items ) ;
if ( layout ) {
await this . setState ( { layout , items : this . props . items } ) ;
this . calculateContainer ( ) ;
if ( ! this . state . editing ) {
this . props . onSaveLayout (
{
ver : "2.0" ,
fileNames : this . state . fileNames ,
defaultLayout : this . state . defaultLayout ,
layout ,
} ,
true
) ;
}
}
}
} ;
_handleRemoteEditObject = async ( { detail } ) => {
const { object } = detail ;
const items = [ ... this . state . items ] ;
for ( let i = 0 ; i < items . length ; i ++ ) {
if ( items [ i ] . id === object . id ) {
items [ i ] = object ;
break ;
}
}
this . setState ( { items } ) ;
} ;
2020-10-22 01:54:01 +03:00
debounce = ( func , ms ) => {
let timer ;
return ( ) => {
window . clearTimeout ( timer ) ;
timer = window . setTimeout ( func , ms ) ;
} ;
} ;
_recalculate = async ( ) => {
let prevUnit = this . state . unit ;
await this . calculateUnit ( ) ;
this . setState ( { containerHeight : this . state . containerHeight * ( this . state . unit / prevUnit ) } ) ;
} ;
2020-10-05 00:30:28 +03:00
calculateUnit = ( ) => {
let ref = this . _ref ;
if ( ! ref ) {
return ;
}
let unit = ref . clientWidth / CONTAINER _SIZE ;
if ( unit === 0 ) {
return ;
}
this . setState ( { unit } ) ;
} ;
calculateContainer = ( ) => {
let highestPoints = this . state . layout . map ( ( pos ) => {
return pos . y + pos . h ;
} ) ;
let containerHeight = Math . max ( ... highestPoints ) * this . state . unit ;
this . setState ( { containerHeight } ) ;
} ;
repairLayout = async ( items , layouts ) => {
2020-10-22 01:54:01 +03:00
let defaultLayout = layouts ? layouts . defaultLayout : this . state . defaultLayout ;
2020-10-05 00:30:28 +03:00
let fileNames = layouts ? layouts . fileNames : this . state . fileNames ;
2020-10-22 01:54:01 +03:00
let layout = layouts ? this . cloneLayout ( layouts . layout ) : this . cloneLayout ( this . state . layout ) ;
2020-10-05 00:30:28 +03:00
let layoutIds = layout . map ( ( pos ) => pos . id ) ;
let repairNeeded = false ;
if ( items . length === layout . length ) {
let itemIds = items . map ( ( item ) => item . id ) ;
for ( let i = 0 ; i < itemIds . length ; i ++ ) {
if ( itemIds [ i ] !== layoutIds [ i ] ) {
repairNeeded = true ;
break ;
}
}
if ( ! repairNeeded ) {
return ;
}
}
let newLayout = items . map ( ( item ) => null ) || [ ] ;
for ( let i = 0 ; i < items . length ; i ++ ) {
let layoutIndex = layoutIds . indexOf ( items [ i ] . id ) ;
if ( layoutIndex === - 1 ) {
continue ;
} else {
newLayout [ i ] = layout [ layoutIndex ] ;
}
}
let added = [ ] ;
for ( let i = 0 ; i < newLayout . length ; i ++ ) {
if ( ! newLayout [ i ] ) {
added . push ( items [ i ] ) ;
}
}
let results = await Promise . allSettled ( added . map ( ( item ) => preload ( item ) ) ) ;
let heights = results . map ( ( result ) => {
if ( result . status === "fulfilled" ) {
return result . value ;
} else {
return 200 ;
}
} ) ;
let yMax ;
if ( ! defaultLayout ) {
let highestPoints = layout . map ( ( pos ) => {
return pos . y + pos . h ;
} ) ;
yMax = Math . max ( ... highestPoints ) + MARGIN ;
if ( fileNames ) {
yMax += TAG _HEIGHT ;
}
}
let h = 0 ;
for ( let i = 0 ; i < newLayout . length ; i ++ ) {
if ( ! newLayout [ i ] ) {
let itemAbove = h - 5 < 0 ? null : newLayout [ i - 5 ] ;
let height = heights [ h ] ;
newLayout [ i ] = {
2020-10-22 01:54:01 +03:00
x : defaultLayout ? ( i % 5 ) * ( SIZE + MARGIN ) : ( h % 5 ) * ( SIZE + MARGIN ) ,
2020-10-05 00:30:28 +03:00
y : defaultLayout
? 0
: itemAbove
? fileNames
? itemAbove . y + itemAbove . h + MARGIN + TAG _HEIGHT
: itemAbove . y + itemAbove . h + MARGIN
: yMax ,
h : height ,
w : SIZE ,
z : 0 ,
id : items [ i ] . id ,
} ;
h += 1 ;
}
}
if ( defaultLayout ) {
for ( let i = 0 ; i < newLayout . length ; i ++ ) {
let itemAbove = i - 5 < 0 ? null : newLayout [ i - 5 ] ;
newLayout [ i ] . x = ( i % 5 ) * ( SIZE + MARGIN ) ;
newLayout [ i ] . y = itemAbove
? fileNames
? itemAbove . y + itemAbove . h + MARGIN + TAG _HEIGHT
: itemAbove . y + itemAbove . h + MARGIN
: 0 ;
}
}
return newLayout ;
} ;
calculateLayout = async ( oldLayout ) => {
let heights = await this . calculateHeights ( ) ;
let layout = oldLayout ? oldLayout : this . state . layout ;
for ( let i = 0 ; i < this . state . items . length ; i ++ ) {
let height = heights [ i ] ;
if ( height === 0 ) continue ;
let itemAbove = i - 5 < 0 ? null : layout [ i - 5 ] ;
layout [ i ] = {
x : ( i % 5 ) * ( SIZE + MARGIN ) ,
y : itemAbove
? this . state . fileNames
? itemAbove . y + itemAbove . h + MARGIN + TAG _HEIGHT
: itemAbove . y + itemAbove . h + MARGIN
: 0 ,
w : SIZE ,
2020-10-22 01:54:01 +03:00
h : oldLayout && oldLayout . length > i ? oldLayout [ i ] . h || height : height ,
2020-10-05 00:30:28 +03:00
z : 0 ,
id : this . state . items [ i ] . id ,
} ;
}
return layout ;
} ;
calculateHeights = async ( ) => {
2020-10-22 01:54:01 +03:00
let results = await Promise . allSettled ( this . state . items . map ( ( item , i ) => preload ( item , i ) ) ) ;
2020-10-05 00:30:28 +03:00
let heights = results . map ( ( result ) => {
if ( result . status === "fulfilled" ) {
return result . value ;
} else {
return 200 ;
}
} ) ;
return heights ;
} ;
cloneLayout = ( layout ) => {
let copy = [ ] ;
for ( let pos of layout ) {
copy . push ( { ... pos } ) ;
}
return copy ;
} ;
_toggleEditing = async ( e , discardChanges ) => {
if ( this . state . editing ) {
window . removeEventListener ( "keydown" , this . _handleKeyDown ) ;
window . removeEventListener ( "keyup" , this . _handleKeyUp ) ;
if ( discardChanges ) {
2020-10-22 01:54:01 +03:00
let layout = await this . repairLayout ( this . state . items , this . state . savedProperties ) ;
2020-10-05 00:30:28 +03:00
let { fileNames , defaultLayout } = this . state . savedProperties ;
if (
layout ||
fileNames !== this . state . fileNames ||
defaultLayout !== this . state . defaultLayout
) {
this . props . onSaveLayout (
{
ver : "2.0" ,
fileNames ,
defaultLayout ,
layout ,
} ,
true
) ;
}
await this . setState ( {
editing : false ,
fileNames ,
defaultLayout ,
layout : layout ? layout : this . state . savedProperties . layout ,
prevLayouts : [ ] ,
} ) ;
this . calculateContainer ( ) ;
} else {
await this . setState ( { editing : false , prevLayouts : [ ] } ) ;
}
} else {
window . addEventListener ( "keydown" , this . _handleKeyDown ) ;
window . addEventListener ( "keyup" , this . _handleKeyUp ) ;
await this . setState ( {
editing : true ,
savedProperties : {
defaultLayout : this . state . defaultLayout ,
fileNames : this . state . fileNames ,
layout : this . cloneLayout ( this . state . layout ) ,
} ,
} ) ;
}
this . calculateUnit ( ) ;
} ;
_toggleFileNames = ( e ) => {
if ( ! this . state . defaultLayout ) {
this . setState ( {
fileNames : ! this . state . fileNames ,
prevLayouts : [
... this . state . prevLayouts ,
{
defaultLayout : this . state . defaultLayout ,
fileNames : this . state . fileNames ,
layout : this . cloneLayout ( this . state . layout ) ,
} ,
] ,
} ) ;
} else {
let layout = this . cloneLayout ( this . state . layout ) ;
for ( let i = 5 ; i < this . state . items . length ; i ++ ) {
let itemAbove = layout [ i - 5 ] ;
if ( this . state . fileNames ) {
layout [ i ] . y = itemAbove . y + itemAbove . h + MARGIN ;
} else {
layout [ i ] . y = itemAbove . y + itemAbove . h + MARGIN + TAG _HEIGHT ;
}
}
this . setState ( {
layout ,
fileNames : ! this . state . fileNames ,
prevLayouts : [
... this . state . prevLayouts ,
{
defaultLayout : this . state . defaultLayout ,
fileNames : this . state . fileNames ,
layout : this . cloneLayout ( this . state . layout ) ,
} ,
] ,
} ) ;
}
} ;
_handleKeyDown = ( e ) => {
let prevValue = this . keysPressed [ e . key ] ;
this . keysPressed [ e . key ] = true ;
if (
( this . keysPressed [ "Control" ] || this . keysPressed [ "Meta" ] ) &&
this . keysPressed [ "z" ] &&
prevValue !== this . keysPressed [ e . key ]
) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
this . _handleUndo ( ) ;
} else if (
( this . keysPressed [ "Control" ] || this . keysPressed [ "Meta" ] ) &&
this . keysPressed [ "s" ] &&
prevValue !== this . keysPressed [ e . key ]
) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
this . _handleSaveLayout ( ) ;
}
} ;
_handleKeyUp = ( e ) => {
this . keysPressed = { } ;
} ;
_handleUndo = ( ) => {
if ( this . state . prevLayouts . length ) {
let prevLayouts = this . state . prevLayouts ;
let layouts = prevLayouts . pop ( ) ;
this . setState ( { ... layouts , prevLayouts } ) ;
}
} ;
_handleMouseDown = ( e , i ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
let layout = this . cloneLayout ( this . state . layout ) ;
layout [ i ] . z = this . state . zIndexMax ;
this . setState ( {
xStart : e . clientX ,
yStart : e . clientY ,
dragIndex : i ,
origLayout : this . cloneLayout ( layout ) ,
layout ,
zIndexMax : this . state . zIndexMax + 1 ,
prevLayouts : [
... this . state . prevLayouts ,
{
defaultLayout : this . state . defaultLayout ,
fileNames : this . state . fileNames ,
layout : this . cloneLayout ( this . state . layout ) ,
} ,
] ,
} ) ;
window . addEventListener ( "mousemove" , this . _handleDrag ) ;
window . addEventListener ( "mouseup" , this . _handleMouseUp ) ;
} ;
_handleDrag = ( e ) => {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
let layout = this . cloneLayout ( this . state . origLayout ) ;
let pos = layout [ this . state . dragIndex ] ;
let dX = ( e . clientX - this . state . xStart ) / this . state . unit ;
let dY = ( e . clientY - this . state . yStart ) / this . state . unit ;
if ( e . shiftKey ) {
if ( Math . abs ( dY ) > Math . abs ( dX ) ) {
pos . y += dY ;
} else {
pos . x += dX ;
}
} else {
pos . x += dX ;
pos . y += dY ;
}
if ( pos . x >= CONTAINER _SIZE || pos . x + pos . w <= 0 || pos . y + pos . h <= 0 ) {
return ;
}
this . setState ( { layout } ) ;
} ;
_handleMouseUp = ( e ) => {
window . removeEventListener ( "mousemove" , this . _handleDrag ) ;
window . removeEventListener ( "mouseup" , this . _handleMouseUp ) ;
let layout = this . state . layout ;
let pos = layout [ this . state . dragIndex ] ;
if ( ! e . ctrlKey && ! e . metaKey ) {
pos . x = Math . round ( pos . x / 10 ) * 10 ;
pos . y = Math . round ( pos . y / 10 ) * 10 ;
}
let state = { dragIndex : null , layout } ;
if ( this . state . defaultLayout ) {
if ( e . clientX !== this . state . xStart || e . clientX !== this . state . yStart ) {
state . defaultLayout = false ;
}
}
if ( ( pos . y + pos . h ) * this . state . unit > this . state . containerHeight ) {
state . containerHeight = ( pos . y + pos . h ) * this . state . unit ;
}
this . setState ( state ) ;
} ;
_handleMouseDownResize = ( e , i ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
let layout = this . cloneLayout ( this . state . layout ) ;
layout [ i ] . z = this . state . zIndexMax ;
this . setState ( {
xStart : e . clientX ,
yStart : e . clientY ,
dragIndex : i ,
origLayout : this . cloneLayout ( layout ) ,
layout ,
zIndexMax : this . state . zIndexMax + 1 ,
prevLayouts : [
... this . state . prevLayouts ,
{
defaultLayout : this . state . defaultLayout ,
fileNames : this . state . fileNames ,
layout : this . cloneLayout ( this . state . layout ) ,
} ,
] ,
ratio : [ layout [ i ] . w , layout [ i ] . h ] ,
freeRatio : ! this . state . items [ i ] . type . startsWith ( "image/" ) ,
} ) ;
window . addEventListener ( "mousemove" , this . _handleDragResize ) ;
window . addEventListener ( "mouseup" , this . _handleMouseUpResize ) ;
} ;
_handleDragResize = ( e ) => {
let state = { } ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
let layout = this . cloneLayout ( this . state . origLayout ) ;
let pos = layout [ this . state . dragIndex ] ;
let dX = ( e . clientX - this . state . xStart ) / this . state . unit ;
let dY ;
if ( this . state . freeRatio && ! e . shiftKey ) {
dY = ( e . clientY - this . state . yStart ) / this . state . unit ;
} else {
dY = ( dX * this . state . ratio [ 1 ] ) / this . state . ratio [ 0 ] ;
}
pos . w += dX ;
pos . h += dY ;
if (
pos . w < MIN _SIZE ||
pos . h < MIN _SIZE ||
pos . w > CONTAINER _SIZE ||
pos . x >= CONTAINER _SIZE ||
pos . x + pos . w <= 0 ||
pos . y + pos . h <= 0
) {
return ;
}
this . setState ( { layout , ... state } ) ;
} ;
_handleMouseUpResize = ( e ) => {
window . removeEventListener ( "mousemove" , this . _handleDragResize ) ;
window . removeEventListener ( "mouseup" , this . _handleMouseUpResize ) ;
let layout = this . state . layout ;
let pos = layout [ this . state . dragIndex ] ;
if ( ! e . ctrlKey && ! e . metaKey ) {
pos . w = Math . round ( pos . w / 10 ) * 10 ;
if ( this . state . freeRatio ) {
pos . h = Math . round ( pos . h / 10 ) * 10 ;
} else {
pos . h = ( pos . w * this . state . ratio [ 1 ] ) / this . state . ratio [ 0 ] ;
}
}
let state = { dragIndex : null , layout , ratio : null } ;
if ( this . state . defaultLayout ) {
if ( e . clientX !== this . state . xStart || e . clientX !== this . state . yStart ) {
state . defaultLayout = false ;
}
}
if ( ( pos . y + pos . h ) * this . state . unit > this . state . containerHeight ) {
state . containerHeight = ( pos . y + pos . h ) * this . state . unit ;
}
this . setState ( state ) ;
} ;
_handleResetLayout = async ( ) => {
if (
2020-10-22 01:54:01 +03:00
! window . confirm ( "Are you sure you want to reset your layout to the default column layout?" )
2020-10-05 00:30:28 +03:00
) {
return ;
}
let prevLayout = this . cloneLayout ( this . state . layout ) ;
let layout = await this . calculateLayout ( ) ;
this . setState ( {
defaultLayout : true ,
prevLayouts : [
... this . state . prevLayouts ,
{
defaultLayout : this . state . defaultLayout ,
fileNames : this . state . fileNames ,
layout : prevLayout ,
} ,
] ,
layout ,
zIndexMax : 1 ,
} ) ;
} ;
_handleSaveLayout = async ( ) => {
//NOTE(martina): collapses the z-indexes back down to 0 through n-1 (so they don't continuously get higher)
let zIndexes = this . state . layout . map ( ( pos ) => pos . z ) ;
zIndexes = [ ... new Set ( zIndexes ) ] ;
zIndexes . sort ( function ( a , b ) {
return a - b ;
} ) ;
let layout = this . cloneLayout ( this . state . layout ) ;
for ( let pos of layout ) {
pos . z = zIndexes . indexOf ( pos . z ) ;
}
await this . props . onSaveLayout ( {
ver : "2.0" ,
fileNames : this . state . fileNames ,
defaultLayout : this . state . defaultLayout ,
layout : layout ,
} ) ;
await this . setState ( { layout } ) ;
this . _toggleEditing ( ) ;
} ;
_handleCheckBox = ( e ) => {
let checked = this . state . checked ;
if ( e . target . value === false ) {
delete checked [ e . target . name ] ;
this . setState ( { checked } ) ;
return ;
}
this . setState ( {
checked : { ... this . state . checked , [ e . target . name ] : true } ,
} ) ;
} ;
_handleDelete = ( ids ) => {
dispatchCustomEvent ( { name : "remote-delete-object" , detail : { ids } } ) ;
} ;
_handleCopy = ( e , value ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
this . setState ( { copyValue : value } , ( ) => {
this . _input . select ( ) ;
document . execCommand ( "copy" ) ;
} ) ;
} ;
_handleLoginModal = ( e ) => {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
//TODO(martina): add a modal popup that says "login or sign up to use this feature", and automatically redirect to in-client view if already logged in
window . location . pathname = "/_" ;
} ;
_handleSetPreview = ( e , i ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
let url = this . state . items [ i ] . url . replace ( "https://undefined" , "https://" ) ;
if ( this . props . preview === url ) return ;
this . props . onSavePreview ( url ) ;
} ;
_handleDownload = ( e , i ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
this . setState ( { checked : { } } ) ;
//TODO(martina): make a download function that works
} ;
_handleSaveCopy = ( e , i ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
let items = [ ] ;
if ( i !== undefined ) {
items = [
{
ownerId : this . state . items [ i ] . ownerId ,
2020-10-22 01:54:01 +03:00
cid : this . state . items [ i ] . cid || Strings . urlToCid ( this . state . items [ i ] . url ) ,
2020-10-05 00:30:28 +03:00
} ,
] ;
} else {
for ( let i of Object . keys ( this . state . checked ) ) {
items . push ( {
ownerId : this . state . items [ i ] . ownerId ,
2020-10-22 01:54:01 +03:00
cid : this . state . items [ i ] . cid || Strings . urlToCid ( this . state . items [ i ] . url ) ,
2020-10-05 00:30:28 +03:00
} ) ;
}
}
this . props . onSaveCopy ( items ) ;
this . setState ( { checked : { } } ) ;
} ;
_handleAddToSlate = ( e , i ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
let items = [ ] ;
if ( i !== undefined ) {
items = [ this . state . items [ i ] ] ;
} else if ( Object . keys ( this . state . checked ) . length ) {
for ( let index of Object . keys ( this . state . checked ) ) {
items . push ( this . state . items [ index ] ) ;
}
}
this . setState ( { checked : { } } ) ;
this . props . onAction ( {
type : "SIDEBAR" ,
value : "SIDEBAR_ADD_FILE_TO_SLATE" ,
data : { files : items , fromSlate : true } ,
} ) ;
} ;
_handleRemoveFromSlate = ( e , i ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
let ids = [ ] ;
if ( i !== undefined ) {
ids = [ this . state . items [ i ] . id ] ;
} else {
for ( let index of Object . keys ( this . state . checked ) ) {
ids . push ( this . state . items [ index ] . id ) ;
}
}
this . props . onRemoveFromSlate ( { detail : { ids } } ) ;
this . setState ( { checked : { } } ) ;
} ;
_handleDeleteFiles = ( e , i ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
let ids = [ ] ;
if ( i !== undefined ) {
ids = [ this . state . items [ i ] . id ] ;
} else {
for ( let index of Object . keys ( this . state . checked ) ) {
ids . push ( this . state . items [ index ] . id ) ;
}
}
let cids = [ ] ;
for ( let file of this . props . viewer . library [ 0 ] . children ) {
if ( ids . includes ( file . id ) ) {
cids . push ( file . cid || file . ipfs . replace ( "/ipfs/" , "" ) ) ;
}
}
this . props . onDeleteFiles ( cids ) ;
this . setState ( { checked : { } } ) ;
} ;
_stopProp = ( e ) => {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
} ;
render ( ) {
let numChecked = Object . keys ( this . state . checked ) . length ;
let unit = this . state . unit ;
return (
< div >
{ this . props . isOwner ? (
this . state . editing ? (
< div
css = { STYLES _BUTTONS _ROW }
style = { { marginBottom : 16 , justifyContent : "space-between" } }
>
2020-10-22 01:54:01 +03:00
< div css = { STYLES _BUTTONS _ROW } style = { { width : "100%" , minWidth : "10%" } } >
< div css = { STYLES _TOGGLE _BOX } style = { { marginRight : 16 , paddingLeft : 24 } } >
2020-10-05 00:30:28 +03:00
< span
style = { {
fontFamily : Constants . font . semiBold ,
fontSize : 14 ,
letterSpacing : "0.2px" ,
} }
>
Display titles
< / s p a n >
< div
style = { {
transform : "scale(0.7)" ,
marginLeft : 8 ,
} }
>
2020-10-22 01:54:01 +03:00
< Toggle active = { this . state . fileNames } onChange = { this . _toggleFileNames } / >
2020-10-05 00:30:28 +03:00
< / d i v >
< / d i v >
{ this . state . defaultLayout ? (
< ButtonDisabled
style = { {
marginRight : 16 ,
backgroundColor : Constants . system . white ,
} }
>
Reset layout
< / B u t t o n D i s a b l e d >
) : (
2020-10-22 01:54:01 +03:00
< ButtonSecondary onClick = { this . _handleResetLayout } style = { { marginRight : 16 } } >
2020-10-05 00:30:28 +03:00
Reset layout
< / B u t t o n S e c o n d a r y >
) }
{ this . state . prevLayouts . length ? (
2020-10-22 01:54:01 +03:00
< ButtonSecondary style = { { marginRight : 16 } } onClick = { this . _handleUndo } >
2020-10-05 00:30:28 +03:00
Undo
< / B u t t o n S e c o n d a r y >
) : (
< ButtonDisabled
style = { {
marginRight : 16 ,
backgroundColor : Constants . system . white ,
} }
>
Undo
< / B u t t o n D i s a b l e d >
) }
< / d i v >
< div css = { STYLES _BUTTONS _ROW } style = { { flexShrink : 0 } } >
< ButtonSecondary
onClick = { ( e ) => this . _toggleEditing ( e , true ) }
style = { { cursor : "pointer" , marginLeft : 16 } }
>
Cancel
< / B u t t o n S e c o n d a r y >
< ButtonPrimary
onClick = { this . _handleSaveLayout }
style = { { cursor : "pointer" , marginLeft : 16 } }
>
Save
< / B u t t o n P r i m a r y >
< / d i v >
< / d i v >
) : (
2020-10-22 01:54:01 +03:00
< div css = { STYLES _BUTTONS _ROW } style = { { justifyContent : "flex-end" , marginBottom : 16 } } >
2020-10-05 00:30:28 +03:00
< ButtonSecondary
onClick = { this . _toggleEditing }
style = { { cursor : "pointer" , marginLeft : 16 } }
>
Edit
< / B u t t o n S e c o n d a r y >
{ / * < d i v
css = { STYLES _TOGGLE _BOX }
style = { { marginLeft : 16 , padding : "0px 12px" } }
>
< div
style = { {
padding : 4 ,
display : "flex" ,
alignItems : "center" ,
marginRight : 8 ,
} }
>
< SVG . GridView height = "24px" / >
< / d i v >
< div
style = { { padding : 4 , display : "flex" , alignItems : "center" } }
>
< SVG . TableView height = "24px" / >
< / d i v >
< /div> */ }
< / d i v >
)
) : null }
< div
css = { this . state . editing ? STYLES _EDIT _CONTAINER : null }
style = { { opacity : this . state . show ? 1 : 0 } }
>
< div
2020-10-22 01:54:01 +03:00
css = { this . state . editing ? STYLES _CONTAINER _EDITING : STYLES _CONTAINER }
2020-10-05 00:30:28 +03:00
style = { {
height : this . state . editing
? ` calc(100vh + ${ this . state . containerHeight } px) `
: ` calc(96px + ${ this . state . containerHeight } px) ` ,
backgroundSize : ` ${ ( CONTAINER _SIZE / 108 ) * this . state . unit } px ${
10 * this . state . unit
} px ` ,
2020-10-22 01:54:01 +03:00
backgroundPosition : ` - ${ ( CONTAINER _SIZE / 220 ) * this . state . unit } px - ${
2020-10-05 00:30:28 +03:00
( CONTAINER _SIZE / 220 ) * this . state . unit
2020-10-22 01:54:01 +03:00
} px ` ,
2020-10-05 00:30:28 +03:00
} }
ref = { ( c ) => {
this . _ref = c ;
} }
>
{ this . state . show ? (
this . state . layout . map ( ( pos , i ) => (
< div
css = { this . state . editing ? STYLES _ITEM _EDITING : STYLES _ITEM }
key = { i }
name = { i }
onMouseEnter = { ( ) => this . setState ( { hover : i } ) }
onMouseLeave = { ( ) => this . setState ( { hover : null } ) }
2020-10-22 01:54:01 +03:00
onMouseDown = { this . state . editing ? ( e ) => this . _handleMouseDown ( e , i ) : ( ) => { } }
onClick = { this . state . editing ? ( ) => { } : ( ) => this . props . onSelect ( i ) }
2020-10-05 00:30:28 +03:00
style = { {
top : pos . y * unit ,
left : pos . x * unit ,
width : pos . w * unit ,
2020-10-22 01:54:01 +03:00
height : this . state . fileNames ? ( pos . h + TAG _HEIGHT ) * unit : pos . h * unit ,
2020-10-05 00:30:28 +03:00
zIndex : pos . z ,
2020-10-22 01:54:01 +03:00
boxShadow : this . state . dragIndex === i ? ` 0 0 44px 0 rgba(0, 0, 0, 0.25) ` : null ,
2020-10-05 00:30:28 +03:00
backgroundColor : Constants . system . foreground ,
} }
>
< div >
< SlateMediaObjectPreview
blurhash = { this . state . items [ i ] . blurhash }
iconOnly = { this . state . fileNames }
charCap = { 70 }
type = { this . state . items [ i ] . type }
url = { this . state . items [ i ] . url }
2020-10-22 01:54:01 +03:00
title = { this . state . items [ i ] . title || this . state . items [ i ] . name }
2020-10-05 00:30:28 +03:00
height = { pos . h * unit }
width = { pos . w * unit }
style = { {
height : pos . h * unit ,
width : pos . w * unit ,
background : Constants . system . white ,
} }
imageStyle = { {
width : pos . w * unit ,
height : pos . h * unit ,
maxHeight : "none" ,
} }
/ >
{ numChecked || this . state . hover === i ? (
< div css = { STYLES _MOBILE _HIDDEN } >
{ this . props . external ? null : (
< div
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
onClick = { ( e ) => {
this . _stopProp ( e ) ;
let checked = this . state . checked ;
if ( checked [ i ] ) {
delete checked [ i ] ;
} else {
checked [ i ] = true ;
}
this . setState ( { checked } ) ;
} }
>
< CheckBox
name = { i }
value = { ! ! this . state . checked [ i ] }
onChange = { this . _handleCheckBox }
boxStyle = { {
height : 24 ,
width : 24 ,
backgroundColor : this . state . checked [ i ]
? Constants . system . brand
: "rgba(255, 255, 255, 0.75)" ,
boxShadow : this . state . checked [ i ]
? "none"
: "0 0 0 2px #C3C3C4 inset" ,
} }
style = { {
position : "absolute" ,
top : 8 ,
left : 8 ,
} }
/ >
< / d i v >
) }
{ this . state . editing ? (
< React . Fragment >
2020-10-22 01:54:01 +03:00
{ this . state . tooltip && this . state . tooltip . startsWith ( ` ${ i } - ` ) ? (
2020-10-05 00:30:28 +03:00
< Tooltip
light
style = {
this . state . tooltip === ` ${ i } -remove `
? {
position : "absolute" ,
top : 36 ,
right : 8 ,
}
: this . state . tooltip === ` ${ i } -view `
? {
position : "absolute" ,
bottom : 52 ,
right : "calc(50% + 28px)" ,
}
: this . state . tooltip === ` ${ i } -download `
? {
position : "absolute" ,
bottom : 52 ,
right : "calc(50% - 12px)" ,
}
: {
position : "absolute" ,
bottom : 52 ,
right : "calc(50% - 52px)" ,
color : Constants . system . red ,
}
}
>
{ this . state . tooltip === ` ${ i } -remove `
? "Remove from slate"
: this . state . tooltip === ` ${ i } -view `
? "View file"
: this . state . tooltip === ` ${ i } -download `
? "Download"
: "Delete file" }
< / T o o l t i p >
) : null }
< div
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
2020-10-22 01:54:01 +03:00
onMouseEnter = { ( ) => this . setState ( { tooltip : ` ${ i } -remove ` } ) }
onMouseLeave = { ( ) => this . setState ( { tooltip : null } ) }
2020-10-05 00:30:28 +03:00
onClick = { ( e ) => {
this . _handleRemoveFromSlate ( e , i ) ;
} }
style = { {
position : "absolute" ,
top : 8 ,
right : 8 ,
cursor : "pointer" ,
margin : 0 ,
} }
css = { STYLES _ICON _CIRCLE }
>
< SVG . DismissCircle height = "24px" / >
< / d i v >
< div
css = { STYLES _ICON _ROW }
style = { {
bottom : this . state . fileNames
? ` calc(24px + ${ TAG _HEIGHT } px) `
: "24px" ,
} }
>
< div
css = { STYLES _ICON _CIRCLE }
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
2020-10-22 01:54:01 +03:00
onMouseEnter = { ( ) => this . setState ( { tooltip : ` ${ i } -view ` } ) }
onMouseLeave = { ( ) => this . setState ( { tooltip : null } ) }
2020-10-05 00:30:28 +03:00
onClick = { ( e ) => {
this . _stopProp ( e ) ;
this . props . onSelect ( i ) ;
} }
>
< SVG . Eye height = "16px" / >
< / d i v >
< div
css = { STYLES _ICON _CIRCLE }
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
2020-10-22 01:54:01 +03:00
onMouseEnter = { ( ) => this . setState ( { tooltip : ` ${ i } -download ` } ) }
onMouseLeave = { ( ) => this . setState ( { tooltip : null } ) }
2020-10-05 00:30:28 +03:00
onClick = { ( e ) => {
this . _handleDownload ( e , i ) ;
} }
>
< SVG . Download height = "16px" / >
< / d i v >
< div
css = { STYLES _ICON _CIRCLE }
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
2020-10-22 01:54:01 +03:00
onMouseEnter = { ( ) => this . setState ( { tooltip : ` ${ i } -delete ` } ) }
onMouseLeave = { ( ) => this . setState ( { tooltip : null } ) }
onClick = {
this . state . items [ i ] . ownerId === this . props . viewer . id
? ( e ) => {
this . _handleDeleteFiles ( e , i ) ;
}
: ( ) => { }
2020-10-05 00:30:28 +03:00
}
2020-10-22 01:54:01 +03:00
style = { {
cursor :
this . state . items [ i ] . ownerId === this . props . viewer . id
? "pointer"
: "not-allowed" ,
2020-10-05 00:30:28 +03:00
} }
>
< SVG . Trash
height = "16px"
2020-10-22 01:54:01 +03:00
style = { {
color :
this . state . items [ i ] . ownerId === this . props . viewer . id
? Constants . system . red
: "#999999" ,
} }
2020-10-05 00:30:28 +03:00
/ >
< / d i v >
< / d i v >
< / R e a c t . F r a g m e n t >
) : (
< React . Fragment >
2020-10-22 01:54:01 +03:00
{ this . state . tooltip && this . state . tooltip . startsWith ( ` ${ i } - ` ) ? (
2020-10-05 00:30:28 +03:00
< Tooltip
light
style = {
this . state . tooltip === ` ${ i } -add `
? {
position : "absolute" ,
top : 36 ,
right : 8 ,
}
: this . state . tooltip === ` ${ i } -copy `
? {
position : "absolute" ,
bottom : 52 ,
right : "calc(50% + 28px)" ,
}
: this . state . tooltip === ` ${ i } -download `
? {
position : "absolute" ,
bottom : 52 ,
right : "calc(50% - 12px)" ,
}
: {
position : "absolute" ,
bottom : 52 ,
right : "calc(50% - 52px)" ,
}
}
>
{ this . state . tooltip === ` ${ i } -add `
? "Add to slate"
: this . state . tooltip === ` ${ i } -copy `
? "Copy link"
: this . state . tooltip === ` ${ i } -download `
? "Download"
: this . state . tooltip === ` ${ i } -preview `
? "Make preview image"
: "Save copy" }
< / T o o l t i p >
) : null }
< div
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
2020-10-22 01:54:01 +03:00
onMouseEnter = { ( ) => this . setState ( { tooltip : ` ${ i } -add ` } ) }
onMouseLeave = { ( ) => this . setState ( { tooltip : null } ) }
2020-10-05 00:30:28 +03:00
onClick = {
this . props . external
? this . _handleLoginModal
: ( e ) => {
this . _handleAddToSlate ( e , i ) ;
}
}
style = { {
position : "absolute" ,
top : 8 ,
right : 8 ,
cursor : "pointer" ,
margin : 0 ,
} }
css = { STYLES _ICON _CIRCLE }
>
< SVG . PlusCircle height = "24px" / >
< / d i v >
< div
css = { STYLES _ICON _ROW }
style = { {
bottom : this . state . fileNames
? ` calc(24px + ${ TAG _HEIGHT } px) `
: "24px" ,
} }
>
< DynamicIcon
onClick = { ( e ) => {
this . _handleCopy (
e ,
` ${ this . props . link } /cid: ${ Strings . urlToCid (
this . state . items [ i ] . url
) } ` ||
this . state . items [ i ] . url . replace (
"https://undefined" ,
"https://"
)
) ;
} }
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
2020-10-22 01:54:01 +03:00
onMouseEnter = { ( ) => this . setState ( { tooltip : ` ${ i } -copy ` } ) }
onMouseLeave = { ( ) => this . setState ( { tooltip : null } ) }
2020-10-05 00:30:28 +03:00
successState = {
2020-10-22 01:54:01 +03:00
< SVG . CheckBox height = "16px" style = { { color : "#4b4a4d" } } / >
2020-10-05 00:30:28 +03:00
}
style = { {
height : 24 ,
width : 24 ,
borderRadius : "50%" ,
backgroundColor : "rgba(248, 248, 248, 0.6)" ,
color : "#4b4a4d" ,
display : "flex" ,
alignItems : "center" ,
justifyContent : "center" ,
cursor : "pointer" ,
margin : "0 8px" ,
WebkitBackdropFilter : "blur(25px)" ,
backdropFilter : "blur(25px)" ,
} }
>
2020-10-22 01:54:01 +03:00
< SVG . DeepLink height = "16px" style = { { color : "#4b4a4d" } } / >
2020-10-05 00:30:28 +03:00
< / D y n a m i c I c o n >
< div
css = { STYLES _ICON _CIRCLE }
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
2020-10-22 01:54:01 +03:00
onMouseEnter = { ( ) => this . setState ( { tooltip : ` ${ i } -download ` } ) }
onMouseLeave = { ( ) => this . setState ( { tooltip : null } ) }
2020-10-05 00:30:28 +03:00
onClick = {
this . props . external
? this . _handleLoginModal
: ( e ) => {
this . _handleDownload ( e , i ) ;
}
}
>
< SVG . Download height = "16px" / >
< / d i v >
{ this . props . isOwner ? (
< div
css = { STYLES _ICON _CIRCLE }
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
2020-10-22 01:54:01 +03:00
onMouseEnter = { ( ) => this . setState ( { tooltip : ` ${ i } -preview ` } ) }
onMouseLeave = { ( ) => this . setState ( { tooltip : null } ) }
2020-10-05 00:30:28 +03:00
onClick = {
this . props . external
? this . _handleLoginModal
: this . state . items [ i ] . type &&
2020-10-22 01:54:01 +03:00
this . state . items [ i ] . type . startsWith ( "image/" ) &&
2020-10-05 00:30:28 +03:00
this . state . items [ i ] . size &&
this . state . items [ i ] . size < SIZE _LIMIT
? ( e ) => this . _handleSetPreview ( e , i )
: ( ) => { }
}
style = {
2020-10-21 23:06:44 +03:00
this . props . preview ===
2020-10-22 01:54:01 +03:00
this . state . items [ i ] . url . replace ( "https://undefined" , "https://" )
2020-10-21 23:06:44 +03:00
? {
2020-10-22 01:54:01 +03:00
backgroundColor : "rgba(0, 97, 187, 0.75)" ,
2020-10-21 23:06:44 +03:00
}
: this . state . items [ i ] . type &&
2020-10-22 01:54:01 +03:00
this . state . items [ i ] . type . startsWith ( "image/" ) &&
2020-10-21 23:06:44 +03:00
this . state . items [ i ] . size &&
this . state . items [ i ] . size < SIZE _LIMIT
2020-10-05 00:30:28 +03:00
? { }
: {
color : "#999999" ,
cursor : "not-allowed" ,
}
}
>
{ this . props . preview ===
this . state . items [ i ] . url . replace (
"https://undefined" ,
"https://"
) ? (
2020-10-21 23:06:44 +03:00
< SVG . DesktopEye
height = "16px"
style = { {
color : Constants . system . white ,
} }
/ >
2020-10-05 00:30:28 +03:00
) : (
< SVG . Desktop height = "16px" / >
) }
< / d i v >
) : (
< div
css = { STYLES _ICON _CIRCLE }
onMouseDown = { this . _stopProp }
onMouseUp = { this . _stopProp }
2020-10-22 01:54:01 +03:00
onMouseEnter = { ( ) => this . setState ( { tooltip : ` ${ i } -save ` } ) }
onMouseLeave = { ( ) => this . setState ( { tooltip : null } ) }
2020-10-05 00:30:28 +03:00
onClick = {
this . props . external
? this . _handleLoginModal
: ( e ) => this . _handleSaveCopy ( e , i )
}
>
< SVG . Save height = "16px" / >
< / d i v >
) }
< / d i v >
< / R e a c t . F r a g m e n t >
) }
< / d i v >
) : null }
< / d i v >
{ this . state . fileNames ? (
< div
css = { STYLES _FILE _TAG }
style = { {
fontSize : ` ${ Math . min ( TAG _HEIGHT * unit * 0.7 , 14 ) } px ` ,
height : ` ${ TAG _HEIGHT * unit } px ` ,
} }
>
< span css = { STYLES _FILE _NAME } >
{ this . state . items [ i ] . title || this . state . items [ i ] . name }
< / s p a n >
< span css = { STYLES _FILE _TYPE } >
{ this . state . items [ i ] . name . lastIndexOf ( "." ) !== - 1
? this . state . items [ i ] . name . slice (
this . state . items [ i ] . name . lastIndexOf ( "." )
)
: "" }
< / s p a n >
< / d i v >
) : null }
{ this . state . editing ? (
< div
css = { STYLES _HANDLE _BOX }
onMouseDown = { ( e ) => this . _handleMouseDownResize ( e , i ) }
style = { {
2020-10-22 01:54:01 +03:00
bottom : this . state . fileNames ? ` calc(0px + ${ TAG _HEIGHT } px) ` : 0 ,
2020-10-05 00:30:28 +03:00
right : 0 ,
2020-10-22 01:54:01 +03:00
display :
this . state . hover === i || this . state . dragIndex === i ? "block" : "none" ,
2020-10-05 00:30:28 +03:00
} }
>
< SVG . DragHandle height = "24px" / >
< / d i v >
) : null }
< / d i v >
) )
) : (
< div css = { STYLES _LOADER } >
< LoaderSpinner / >
< / d i v >
) }
< / d i v >
< / d i v >
{ numChecked ? (
< div css = { STYLES _ACTION _BAR } >
< div css = { STYLES _LEFT } >
< span css = { STYLES _FILES _SELECTED } >
{ numChecked } file { numChecked > 1 ? "s" : "" } selected
< / s p a n >
< / d i v >
{ this . props . isOwner ? (
< div css = { STYLES _RIGHT } >
< ButtonPrimary transparent onClick = { this . _handleAddToSlate } >
Add to slate
< / B u t t o n P r i m a r y >
< ButtonPrimary transparent onClick = { this . _handleDownload } >
Download
< / B u t t o n P r i m a r y >
< ButtonWarning
transparent
style = { { marginLeft : 8 } }
onClick = { this . _handleRemoveFromSlate }
loading = {
this . state . loading &&
Object . values ( this . state . loading ) . some ( ( elem ) => {
return ! ! elem ;
} )
}
>
Remove
< / B u t t o n W a r n i n g >
< ButtonWarning
transparent
style = { { marginLeft : 8 } }
onClick = { this . _handleDeleteFiles }
loading = {
this . state . loading &&
Object . values ( this . state . loading ) . some ( ( elem ) => {
return ! ! elem ;
} )
}
>
Delete files
< / B u t t o n W a r n i n g >
2020-10-22 01:54:01 +03:00
< div css = { STYLES _ICON _BOX } onClick = { ( ) => this . setState ( { checked : { } } ) } >
< SVG . Dismiss height = "20px" style = { { color : Constants . system . darkGray } } / >
2020-10-05 00:30:28 +03:00
< / d i v >
< / d i v >
) : (
< div css = { STYLES _RIGHT } >
< ButtonPrimary transparent onClick = { this . _handleAddToSlate } >
Add to slate
< / B u t t o n P r i m a r y >
< ButtonPrimary transparent onClick = { this . _handleSaveCopy } >
Save copy
< / B u t t o n P r i m a r y >
< ButtonPrimary transparent onClick = { this . _handleDownload } >
Download
< / B u t t o n P r i m a r y >
2020-10-22 01:54:01 +03:00
< div css = { STYLES _ICON _BOX } onClick = { ( ) => this . setState ( { checked : { } } ) } >
< SVG . Dismiss height = "20px" style = { { color : Constants . system . darkGray } } / >
2020-10-05 00:30:28 +03:00
< / d i v >
< / d i v >
) }
< / d i v >
) : null }
< input
ref = { ( c ) => {
this . _input = c ;
} }
readOnly
value = { this . state . copyValue }
css = { STYLES _COPY _INPUT }
/ >
< / d i v >
) ;
}
}