Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
N
Neos
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
1
Issues
1
List
Boards
Labels
Service Desk
Milestones
Merge Requests
2
Merge Requests
2
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
Neos
Commits
4af2045b
Commit
4af2045b
authored
May 04, 2023
by
timel
Committed by
Chunchi Che
May 28, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add animation
parent
dcf03b35
Changes
14
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
472 additions
and
319 deletions
+472
-319
src/ui/Duel/Main.tsx
src/ui/Duel/Main.tsx
+0
-1
src/ui/Duel/NewPlayMat/Card/index.scss
src/ui/Duel/NewPlayMat/Card/index.scss
+4
-3
src/ui/Duel/NewPlayMat/Card/index.tsx
src/ui/Duel/NewPlayMat/Card/index.tsx
+85
-227
src/ui/Duel/NewPlayMat/Card/springs/index.ts
src/ui/Duel/NewPlayMat/Card/springs/index.ts
+4
-0
src/ui/Duel/NewPlayMat/Card/springs/toDeck.ts
src/ui/Duel/NewPlayMat/Card/springs/toDeck.ts
+64
-0
src/ui/Duel/NewPlayMat/Card/springs/toField.ts
src/ui/Duel/NewPlayMat/Card/springs/toField.ts
+126
-0
src/ui/Duel/NewPlayMat/Card/springs/toHand.ts
src/ui/Duel/NewPlayMat/Card/springs/toHand.ts
+77
-0
src/ui/Duel/NewPlayMat/Card/springs/toOutside.ts
src/ui/Duel/NewPlayMat/Card/springs/toOutside.ts
+55
-0
src/ui/Duel/NewPlayMat/Card/springs/types.ts
src/ui/Duel/NewPlayMat/Card/springs/types.ts
+16
-0
src/ui/Duel/NewPlayMat/Card/springs/utils.ts
src/ui/Duel/NewPlayMat/Card/springs/utils.ts
+11
-0
src/ui/Duel/NewPlayMat/Mat/index.scss
src/ui/Duel/NewPlayMat/Mat/index.scss
+17
-3
src/ui/Duel/NewPlayMat/Mat/index.tsx
src/ui/Duel/NewPlayMat/Mat/index.tsx
+9
-3
src/ui/Duel/NewPlayMat/utils/cssConfig.ts
src/ui/Duel/NewPlayMat/utils/cssConfig.ts
+4
-0
src/ui/Duel/Test.tsx
src/ui/Duel/Test.tsx
+0
-82
No files found.
src/ui/Duel/Main.tsx
View file @
4af2045b
...
...
@@ -14,7 +14,6 @@ import {
YesNoModal
,
}
from
"
./Message
"
;
import
Mat
from
"
./PlayMat
"
;
import
{
Test
}
from
"
./Test
"
;
import
{
Mat
as
NewMat
}
from
"
./NewPlayMat
"
;
import
{
Menu
}
from
"
./NewPlayMat/Menu
"
;
...
...
src/ui/Duel/NewPlayMat/Card/index.scss
View file @
4af2045b
section
#mat
{
.mat-card
{
position
:
absolute
;
left
:
0
;
top
:
0
;
// left: 50%
;
// top: 50%
;
--card-height
:
100px
;
height
:
var
(
--
card-height
);
aspect-ratio
:
var
(
--
card-ratio
);
...
...
@@ -12,7 +12,8 @@ section#mat {
position
:
relative
;
height
:
100%
;
width
:
100%
;
transform
:
rotateY
(
calc
(
var
(
--
ry
)
*
1deg
));
transform
:
translateZ
(
calc
(
var
(
--
z
)
*
1px
))
rotateY
(
calc
(
var
(
--
ry
)
*
1deg
));
.card-cover
,
.card-back
{
width
:
100%
;
...
...
src/ui/Duel/NewPlayMat/Card/index.tsx
View file @
4af2045b
This diff is collapsed.
Click to expand it.
src/ui/Duel/NewPlayMat/Card/springs/index.ts
0 → 100644
View file @
4af2045b
export
*
from
"
./toField
"
;
export
*
from
"
./toHand
"
;
export
*
from
"
./toDeck
"
;
export
*
from
"
./toOutside
"
;
src/ui/Duel/NewPlayMat/Card/springs/toDeck.ts
0 → 100644
View file @
4af2045b
import
{
isMe
,
type
CardType
}
from
"
@/stores
"
;
import
{
SpringApi
}
from
"
./types
"
;
import
{
matConfig
}
from
"
../../utils
"
;
import
{
ygopro
}
from
"
@/api
"
;
import
{
easings
}
from
"
@react-spring/web
"
;
import
{
asyncStart
}
from
"
./utils
"
;
const
{
PLANE_ROTATE_X
,
BLOCK_WIDTH
,
BLOCK_HEIGHT_M
,
BLOCK_HEIGHT_S
,
CARD_RATIO
,
COL_GAP
,
ROW_GAP
,
HAND_MARGIN_TOP
,
HAND_CARD_HEIGHT
,
HAND_CIRCLE_CENTER_OFFSET_Y
,
DECK_OFFSET_X
,
DECK_OFFSET_Y
,
DECK_ROTATE_Z
,
DECK_CARD_HEIGHT
,
}
=
matConfig
;
const
{
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
,
MZONE
,
SZONE
,
TZONE
,
OVERLAY
}
=
ygopro
.
CardZone
;
export
const
moveToDeck
=
async
(
props
:
{
card
:
CardType
;
api
:
SpringApi
;
report
:
boolean
;
})
=>
{
const
{
card
,
api
,
report
}
=
props
;
// report
const
{
zone
,
sequence
,
controller
,
xyzMonster
,
position
}
=
card
;
const
rightX
=
DECK_OFFSET_X
.
value
+
2
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
const
leftX
=
-
rightX
;
const
bottomY
=
DECK_OFFSET_Y
.
value
+
2
*
BLOCK_HEIGHT_M
.
value
+
BLOCK_HEIGHT_S
.
value
+
2
*
ROW_GAP
.
value
-
BLOCK_HEIGHT_S
.
value
;
const
topY
=
-
bottomY
;
let
x
=
isMe
(
controller
)
?
rightX
:
leftX
;
let
y
=
isMe
(
controller
)
?
bottomY
:
topY
;
if
(
zone
===
EXTRA
)
{
x
=
isMe
(
controller
)
?
leftX
:
rightX
;
}
let
rz
=
isMe
(
controller
)
?
180
-
DECK_ROTATE_Z
.
value
:
-
DECK_ROTATE_Z
.
value
;
if
(
zone
===
EXTRA
)
{
rz
=
isMe
(
controller
)
?
DECK_ROTATE_Z
.
value
:
DECK_ROTATE_Z
.
value
;
}
const
z
=
sequence
;
api
.
start
({
x
,
y
,
z
,
rz
,
zIndex
:
z
,
height
:
DECK_CARD_HEIGHT
.
value
,
});
};
src/ui/Duel/NewPlayMat/Card/springs/toField.ts
0 → 100644
View file @
4af2045b
import
{
isMe
,
type
CardType
}
from
"
@/stores
"
;
import
{
SpringApi
}
from
"
./types
"
;
import
{
matConfig
}
from
"
../../utils
"
;
import
{
ygopro
}
from
"
@/api
"
;
import
{
easings
}
from
"
@react-spring/web
"
;
import
{
asyncStart
}
from
"
./utils
"
;
const
{
PLANE_ROTATE_X
,
BLOCK_WIDTH
,
BLOCK_HEIGHT_M
,
BLOCK_HEIGHT_S
,
CARD_RATIO
,
COL_GAP
,
ROW_GAP
,
HAND_MARGIN_TOP
,
HAND_CARD_HEIGHT
,
HAND_CIRCLE_CENTER_OFFSET_Y
,
DECK_OFFSET_X
,
DECK_OFFSET_Y
,
DECK_ROTATE_Z
,
}
=
matConfig
;
const
{
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
,
MZONE
,
SZONE
,
TZONE
,
OVERLAY
}
=
ygopro
.
CardZone
;
export
const
moveToField
=
async
(
props
:
{
card
:
CardType
;
api
:
SpringApi
;
report
:
boolean
;
})
=>
{
const
{
card
,
api
,
report
}
=
props
;
// report
const
{
zone
,
sequence
,
controller
,
xyzMonster
,
position
,
overlayMaterials
}
=
card
;
// 根据zone计算卡片的宽度
const
cardWidth
=
zone
===
SZONE
?
BLOCK_HEIGHT_S
.
value
*
CARD_RATIO
.
value
:
BLOCK_HEIGHT_M
.
value
*
CARD_RATIO
.
value
;
const
height
=
zone
===
SZONE
?
BLOCK_HEIGHT_S
.
value
:
BLOCK_HEIGHT_M
.
value
;
// 首先计算 x 和 y
let
x
=
0
,
y
=
0
;
switch
(
zone
)
{
case
SZONE
:
{
if
(
sequence
===
5
)
{
// 场地魔法
x
=
-
(
3
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
)
-
(
BLOCK_WIDTH
.
value
-
cardWidth
)
/
2
);
y
=
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
;
}
else
{
x
=
(
sequence
-
2
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
y
=
2
*
(
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
)
-
(
BLOCK_HEIGHT_M
.
value
-
BLOCK_HEIGHT_S
.
value
)
/
2
;
}
break
;
}
case
MZONE
:
{
if
(
sequence
>
4
)
{
// 额外怪兽区
x
=
(
sequence
>
5
?
1
:
-
1
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
y
=
0
;
}
else
{
x
=
(
sequence
-
2
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
y
=
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
;
}
break
;
}
case
OVERLAY
:
{
if
(
xyzMonster
)
{
const
{
sequence
}
=
xyzMonster
;
if
(
sequence
>
4
)
{
// 额外怪兽区
x
=
(
sequence
>
5
?
1
:
-
1
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
y
=
0
;
}
else
{
x
=
(
sequence
-
2
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
y
=
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
;
}
}
break
;
}
}
if
(
!
isMe
(
controller
))
{
x
=
-
x
;
y
=
-
y
;
}
await
asyncStart
(
api
)({
x
,
y
,
height
,
z
:
overlayMaterials
.
length
?
200
:
120
,
ry
:
[
ygopro
.
CardPosition
.
FACEDOWN
,
ygopro
.
CardPosition
.
FACEDOWN_ATTACK
,
ygopro
.
CardPosition
.
FACEDOWN_DEFENSE
,
].
includes
(
position
??
5
)
?
180
:
0
,
rz
:
isMe
(
controller
)
?
0
:
180
,
config
:
{
// mass: 0.5,
easing
:
easings
.
easeOutSine
,
},
});
await
asyncStart
(
api
)({
z
:
0
,
zIndex
:
overlayMaterials
.
length
?
3
:
1
,
config
:
{
easing
:
easings
.
easeInSine
,
mass
:
5
,
tension
:
300
,
// 170
friction
:
12
,
// 26
clamp
:
true
,
},
});
};
src/ui/Duel/NewPlayMat/Card/springs/toHand.ts
0 → 100644
View file @
4af2045b
import
{
isMe
,
type
CardType
,
cardStore
}
from
"
@/stores
"
;
import
{
SpringApi
,
ReportEnum
}
from
"
./types
"
;
import
{
matConfig
}
from
"
../../utils
"
;
import
{
ygopro
}
from
"
@/api
"
;
import
{
easings
}
from
"
@react-spring/web
"
;
import
{
asyncStart
}
from
"
./utils
"
;
const
{
PLANE_ROTATE_X
,
BLOCK_WIDTH
,
BLOCK_HEIGHT_M
,
BLOCK_HEIGHT_S
,
CARD_RATIO
,
COL_GAP
,
ROW_GAP
,
HAND_MARGIN_TOP
,
HAND_CARD_HEIGHT
,
HAND_CIRCLE_CENTER_OFFSET_Y
,
DECK_OFFSET_X
,
DECK_OFFSET_Y
,
DECK_ROTATE_Z
,
}
=
matConfig
;
const
{
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
,
MZONE
,
SZONE
,
TZONE
,
OVERLAY
}
=
ygopro
.
CardZone
;
export
const
moveToHand
=
async
(
props
:
{
card
:
CardType
;
api
:
SpringApi
;
report
:
boolean
;
})
=>
{
const
{
card
,
api
,
report
}
=
props
;
const
{
zone
,
sequence
,
controller
}
=
card
;
// 得刷新除了这个卡以外所有的自己的手卡
if
(
report
)
{
eventBus
.
emit
(
ReportEnum
.
ReloadHand
,
{
controller
,
sequence
,
});
}
// 手卡会有很复杂的计算...
const
hand_circle_center_x
=
0
;
const
hand_circle_center_y
=
1
*
BLOCK_HEIGHT_M
.
value
+
1
*
BLOCK_HEIGHT_S
.
value
+
2
*
ROW_GAP
.
value
+
(
HAND_MARGIN_TOP
.
value
+
HAND_CARD_HEIGHT
.
value
+
HAND_CIRCLE_CENTER_OFFSET_Y
.
value
);
const
hand_card_width
=
CARD_RATIO
.
value
*
HAND_CARD_HEIGHT
.
value
;
const
THETA
=
2
*
Math
.
atan
(
hand_card_width
/
2
/
(
HAND_CIRCLE_CENTER_OFFSET_Y
.
value
+
HAND_CARD_HEIGHT
.
value
)
)
*
0.9
;
// 接下来计算每一张手卡
const
hands_length
=
cardStore
.
at
(
HAND
,
controller
).
length
;
const
angle
=
(
sequence
-
(
hands_length
-
1
)
/
2
)
*
THETA
;
const
r
=
HAND_CIRCLE_CENTER_OFFSET_Y
.
value
+
HAND_CARD_HEIGHT
.
value
/
2
;
const
negativeX
=
Math
.
sin
(
angle
)
*
r
;
const
negativeY
=
Math
.
cos
(
angle
)
*
r
+
HAND_CARD_HEIGHT
.
value
/
2
;
const
x
=
hand_circle_center_x
+
negativeX
*
(
isMe
(
controller
)
?
1
:
-
1
);
const
y
=
hand_circle_center_y
-
negativeY
+
130
;
// 常量 是手动调的 这里肯定有问题 有空来修
const
_rz
=
(
angle
*
180
)
/
Math
.
PI
;
api
.
start
({
x
:
isMe
(
controller
)
?
x
:
-
x
,
y
:
isMe
(
controller
)
?
y
:
-
y
,
rz
:
isMe
(
controller
)
?
_rz
:
180
-
_rz
,
height
:
HAND_CARD_HEIGHT
.
value
,
// rx: -PLANE_ROTATE_X.value,
});
};
src/ui/Duel/NewPlayMat/Card/springs/toOutside.ts
0 → 100644
View file @
4af2045b
import
{
isMe
,
type
CardType
}
from
"
@/stores
"
;
import
{
SpringApi
}
from
"
./types
"
;
import
{
matConfig
}
from
"
../../utils
"
;
import
{
ygopro
}
from
"
@/api
"
;
import
{
easings
}
from
"
@react-spring/web
"
;
import
{
asyncStart
}
from
"
./utils
"
;
const
{
PLANE_ROTATE_X
,
BLOCK_WIDTH
,
BLOCK_HEIGHT_M
,
BLOCK_HEIGHT_S
,
CARD_RATIO
,
COL_GAP
,
ROW_GAP
,
HAND_MARGIN_TOP
,
HAND_CARD_HEIGHT
,
HAND_CIRCLE_CENTER_OFFSET_Y
,
DECK_OFFSET_X
,
DECK_OFFSET_Y
,
DECK_ROTATE_Z
,
}
=
matConfig
;
const
{
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
,
MZONE
,
SZONE
,
TZONE
,
OVERLAY
}
=
ygopro
.
CardZone
;
export
const
moveToOutside
=
async
(
props
:
{
card
:
CardType
;
api
:
SpringApi
;
report
:
boolean
;
})
=>
{
const
{
card
,
api
,
report
}
=
props
;
// report
const
{
zone
,
sequence
,
controller
,
xyzMonster
,
position
,
overlayMaterials
}
=
card
;
let
x
=
0
,
y
=
0
;
if
(
zone
===
GRAVE
)
{
x
=
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
)
*
3
;
y
=
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
;
}
else
if
(
zone
===
REMOVED
)
{
x
=
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
)
*
2
;
}
if
(
!
isMe
(
controller
))
{
x
=
-
x
;
y
=
-
y
;
}
api
.
start
({
x
,
y
,
z
:
0
,
rz
:
isMe
(
controller
)
?
0
:
180
,
});
};
src/ui/Duel/NewPlayMat/Card/springs/types.ts
0 → 100644
View file @
4af2045b
import
{
type
SpringValue
,
type
SpringRef
}
from
"
@react-spring/web
"
;
export
type
SpringApi
=
SpringRef
<
{
x
:
number
;
y
:
number
;
z
:
number
;
rx
:
number
;
ry
:
number
;
rz
:
number
;
zIndex
:
number
;
height
:
number
;
}
>
;
export
enum
ReportEnum
{
ReloadHand
=
"
reload-hand
"
,
}
src/ui/Duel/NewPlayMat/Card/springs/utils.ts
0 → 100644
View file @
4af2045b
import
{
type
SpringRef
,
type
SpringConfig
}
from
"
@react-spring/web
"
;
export
const
asyncStart
=
<
T
extends
{}
>
(
api
:
SpringRef
<
T
>
)
=>
{
return
(
p
:
Partial
<
T
>
&
{
config
:
SpringConfig
})
=>
new
Promise
((
resolve
)
=>
{
api
.
start
({
...
p
,
onRest
:
resolve
,
});
});
};
src/ui/Duel/NewPlayMat/Mat/index.scss
View file @
4af2045b
section
#mat
{
margin-top
:
200px
;
//
margin-top: 200px;
// padding-top: 50px; // 先不管 后面调整
position
:
relative
;
#camera
{
...
...
@@ -8,12 +8,26 @@ section#mat {
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
perspective
:
var
(
--
perspective
);
//
perspective: var(--perspective);
}
#plane
{
transform
:
translateX
(
0
)
translateY
(
0
)
translateZ
(
0
)
rotateX
(
var
(
--
plane-rotate-x
));
width
:
fit-content
;
transform-style
:
preserve-3d
;
perspective
:
var
(
--
perspective
);
}
}
.mat-card-container
{
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
100%
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
transform-style
:
preserve-3d
;
}
src/ui/Duel/NewPlayMat/Mat/index.tsx
View file @
4af2045b
...
...
@@ -22,9 +22,11 @@ export const Mat: FC = () => {
>
<
Plane
>
<
Bg
/>
{
snap
.
map
((
cardSnap
,
i
)
=>
(
<
Card
key=
{
i
}
idx=
{
i
}
/>
))
}
<
CardContainer
>
{
snap
.
map
((
cardSnap
,
i
)
=>
(
<
Card
key=
{
i
}
idx=
{
i
}
/>
))
}
</
CardContainer
>
</
Plane
>
</
section
>
);
...
...
@@ -35,3 +37,7 @@ const Plane: FC<PropsWithChildren> = ({ children }) => (
<
div
id=
"plane"
>
{
children
}
</
div
>
</
div
>
);
const
CardContainer
:
FC
<
PropsWithChildren
>
=
({
children
})
=>
(
<
div
className=
"mat-card-container"
>
{
children
}
</
div
>
);
src/ui/Duel/NewPlayMat/utils/cssConfig.ts
View file @
4af2045b
...
...
@@ -76,4 +76,8 @@ export const matConfig = {
value
:
30
,
unit
:
UNIT
.
DEG
,
},
DECK_CARD_HEIGHT
:
{
value
:
120
,
unit
:
UNIT
.
PX
,
},
};
src/ui/Duel/Test.tsx
deleted
100644 → 0
View file @
dcf03b35
import
{
cardStore
}
from
"
@/stores
"
;
import
{
useSnapshot
}
from
"
valtio
"
;
import
{
subscribeKey
,
watch
}
from
"
valtio/utils
"
;
import
{
FC
,
memo
,
useEffect
,
useState
}
from
"
react
"
;
import
{
ygopro
}
from
"
@/api
"
;
import
{
useSpring
,
SpringValue
,
animated
,
useSpringRef
,
}
from
"
@react-spring/web
"
;
export
const
Test
=
()
=>
{
const
snap
=
useSnapshot
(
cardStore
.
inner
);
return
(
<
div
style=
{
{
background
:
"
white
"
,
position
:
"
fixed
"
,
left
:
0
,
top
:
0
,
color
:
"
black
"
,
zIndex
:
9999
,
fontSize
:
12
,
}
}
>
{
snap
.
map
((
cardState
,
i
)
=>
(
<
Card
idx=
{
i
}
key=
{
i
}
show=
{
[
ygopro
.
CardZone
.
HAND
,
ygopro
.
CardZone
.
MZONE
,
ygopro
.
CardZone
.
SZONE
,
ygopro
.
CardZone
.
GRAVE
,
].
includes
(
cardState
.
zone
)
}
/>
))
}
</
div
>
);
};
export
const
Card
:
FC
<
{
idx
:
number
;
show
:
boolean
}
>
=
memo
(
({
idx
,
show
})
=>
{
const
snap
=
useSnapshot
(
cardStore
.
inner
[
idx
]);
const
api
=
useSpringRef
();
const
props
=
useSpring
({
ref
:
api
,
from
:
{
x
:
0
},
});
// subscribeKey(cardStore.inner[idx], "zone", (value) => {
// api.start({
// to: {
// x: value * 100,
// },
// });
// });
watch
((
get
)
=>
{
get
(
cardStore
.
inner
[
idx
]);
const
zone
=
get
(
cardStore
.
inner
[
idx
]).
zone
;
api
.
start
({
to
:
{
x
:
zone
*
100
,
},
});
});
return
show
?
(
<
animated
.
div
style=
{
{
transform
:
props
.
x
.
to
((
value
)
=>
`translateX(${value}px)`
),
background
:
"
white
"
,
}
}
>
<
div
>
code:
{
snap
.
code
}
</
div
>
<
div
>
{
(
Math
.
random
()
*
100
).
toFixed
(
0
)
}
</
div
>
</
animated
.
div
>
)
:
(
<></>
);
},
(
prev
,
next
)
=>
prev
.
show
===
next
.
show
// 只有 show 变化时才会重新渲染
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment