Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
84e01b3e
Commit
84e01b3e
authored
Feb 20, 2018
by
Jose Ivan Vargas
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Changed selector names, address code concerns
parent
d5c00186
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
183 additions
and
185 deletions
+183
-185
app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
.../milestones/shared/components/promote_milestone_modal.vue
+5
-5
app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js
...ts/pages/milestones/shared/delete_milestone_modal_init.js
+4
-6
app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js
...s/pages/milestones/shared/promote_milestone_modal_init.js
+62
-62
app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
.../pages/projects/labels/components/promote_label_modal.vue
+17
-9
app/assets/javascripts/pages/projects/labels/index/index.js
app/assets/javascripts/pages/projects/labels/index/index.js
+67
-65
app/assets/stylesheets/framework/modal.scss
app/assets/stylesheets/framework/modal.scss
+4
-2
app/views/projects/labels/index.html.haml
app/views/projects/labels/index.html.haml
+1
-1
app/views/projects/milestones/show.html.haml
app/views/projects/milestones/show.html.haml
+3
-3
app/views/shared/_label.html.haml
app/views/shared/_label.html.haml
+2
-1
app/views/shared/milestones/_milestone.html.haml
app/views/shared/milestones/_milestone.html.haml
+2
-2
changelogs/unreleased/jivl-new-modal-project-labels-milestones.yml
...s/unreleased/jivl-new-modal-project-labels-milestones.yml
+1
-1
spec/javascripts/pages/labels/components/promote_label_modal_spec.js
...ripts/pages/labels/components/promote_label_modal_spec.js
+8
-14
spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
...estones/shared/components/promote_milestone_modal_spec.js
+7
-14
No files found.
app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
View file @
84e01b3e
...
...
@@ -22,7 +22,7 @@
},
computed
:
{
title
()
{
return
sprintf
(
s__
(
'
Milestones|Promote %{
title} to group milestone?
'
),
{
t
itle
:
this
.
milestoneTitle
});
return
sprintf
(
s__
(
'
Milestones|Promote %{
milestoneTitle} to group milestone?
'
),
{
milestoneT
itle
:
this
.
milestoneTitle
});
},
text
()
{
return
s__
(
`Milestones|Promoting this milestone will make it available for all projects inside the group.
...
...
@@ -35,11 +35,11 @@
eventHub
.
$emit
(
'
promoteMilestoneModal.requestStarted
'
,
this
.
url
);
return
axios
.
post
(
this
.
url
)
.
then
((
response
)
=>
{
eventHub
.
$emit
(
'
promoteMilestoneModal.requestFinished
'
,
{
label
Url
:
this
.
url
,
successful
:
true
});
eventHub
.
$emit
(
'
promoteMilestoneModal.requestFinished
'
,
{
milestone
Url
:
this
.
url
,
successful
:
true
});
redirectTo
(
response
.
request
.
responseURL
);
})
.
catch
((
error
)
=>
{
eventHub
.
$emit
(
'
promoteMilestoneModal.requestFinished
'
,
{
labelUrl
:
this
.
url
,
successful
:
tru
e
});
eventHub
.
$emit
(
'
promoteMilestoneModal.requestFinished
'
,
{
milestoneUrl
:
this
.
url
,
successful
:
fals
e
});
createFlash
(
error
);
});
},
...
...
@@ -53,11 +53,11 @@
:footer-primary-button-text=
"s__('Milestones|Promote Milestone')"
@
submit=
"onSubmit"
>
<
div
<
template
slot=
"title"
>
{{
title
}}
</
div
>
</
template
>
{{ text }}
</gl-modal>
</template>
...
...
app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js
View file @
84e01b3e
...
...
@@ -37,16 +37,14 @@ export default () => {
};
const
deleteMilestoneButtons
=
document
.
querySelectorAll
(
'
.js-delete-milestone-button
'
);
for
(
let
i
=
0
;
i
<
deleteMilestoneButtons
.
length
;
i
+=
1
)
{
const
button
=
deleteMilestoneButtons
[
i
];
deleteMilestoneButtons
.
forEach
((
button
)
=>
{
button
.
addEventListener
(
'
click
'
,
onDeleteButtonClick
);
}
}
);
eventHub
.
$once
(
'
deleteMilestoneModal.mounted
'
,
()
=>
{
for
(
let
i
=
0
;
i
<
deleteMilestoneButtons
.
length
;
i
+=
1
)
{
const
button
=
deleteMilestoneButtons
[
i
];
deleteMilestoneButtons
.
forEach
((
button
)
=>
{
button
.
removeAttribute
(
'
disabled
'
);
}
}
);
});
return
new
Vue
({
...
...
app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js
View file @
84e01b3e
...
...
@@ -5,21 +5,22 @@ import eventHub from './event_hub';
Vue
.
use
(
Translate
);
const
onRequestFinished
=
({
milestoneUrl
,
successful
})
=>
{
const
button
=
document
.
querySelector
(
`.js-promote-project-milestone[data-url="
${
milestoneUrl
}
"]`
);
export
default
()
=>
{
const
onRequestFinished
=
({
milestoneUrl
,
successful
})
=>
{
const
button
=
document
.
querySelector
(
`.js-promote-project-milestone-button[data-url="
${
milestoneUrl
}
"]`
);
if
(
!
successful
)
{
button
.
removeAttribute
(
'
disabled
'
);
}
};
};
const
onRequestStarted
=
(
milestoneUrl
)
=>
{
const
button
=
document
.
querySelector
(
`.js-promote-project-milestone
[data-url="
${
milestoneUrl
}
"]`
);
const
onRequestStarted
=
(
milestoneUrl
)
=>
{
const
button
=
document
.
querySelector
(
`.js-promote-project-milestone-button
[data-url="
${
milestoneUrl
}
"]`
);
button
.
setAttribute
(
'
disabled
'
,
''
);
eventHub
.
$once
(
'
promoteMilestoneModal.requestFinished
'
,
onRequestFinished
);
};
};
const
onDeleteButtonClick
=
(
event
)
=>
{
const
onDeleteButtonClick
=
(
event
)
=>
{
const
button
=
event
.
currentTarget
;
const
modalProps
=
{
milestoneTitle
:
button
.
dataset
.
milestoneTitle
,
...
...
@@ -27,22 +28,25 @@ const onDeleteButtonClick = (event) => {
};
eventHub
.
$once
(
'
promoteMilestoneModal.requestStarted
'
,
onRequestStarted
);
eventHub
.
$emit
(
'
promoteMilestoneModal.props
'
,
modalProps
);
};
};
const
promoteMilestoneButtons
=
document
.
querySelectorAll
(
'
.js-promote-project-milestone
'
);
promoteMilestoneButtons
.
forEach
((
button
)
=>
{
const
promoteMilestoneButtons
=
document
.
querySelectorAll
(
'
.js-promote-project-milestone-button
'
);
promoteMilestoneButtons
.
forEach
((
button
)
=>
{
button
.
addEventListener
(
'
click
'
,
onDeleteButtonClick
);
});
});
eventHub
.
$once
(
'
promoteMilestoneModal.mounted
'
,
()
=>
{
eventHub
.
$once
(
'
promoteMilestoneModal.mounted
'
,
()
=>
{
promoteMilestoneButtons
.
forEach
((
button
)
=>
{
button
.
removeAttribute
(
'
disabled
'
);
});
});
});
export
default
()
=>
{
const
promoteMilestoneComponent
=
new
Vue
({
el
:
'
#promote-milestone-modal
'
,
const
promoteMilestoneModal
=
document
.
getElementById
(
'
promote-milestone-modal
'
);
let
promoteMilestoneComponent
;
if
(
promoteMilestoneModal
)
{
promoteMilestoneComponent
=
new
Vue
({
el
:
promoteMilestoneModal
,
components
:
{
PromoteMilestoneModal
,
},
...
...
@@ -72,11 +76,7 @@ export default () => {
});
},
});
const
promoteMilestoneModal
=
document
.
getElementById
(
'
promote-milestone-modal
'
);
let
withMilestone
;
if
(
promoteMilestoneModal
!=
null
)
{
withMilestone
=
promoteMilestoneComponent
;
}
return
withMilestone
;
return
promoteMilestoneComponent
;
};
app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
View file @
84e01b3e
...
...
@@ -3,7 +3,7 @@
import
createFlash
from
'
~/flash
'
;
import
GlModal
from
'
~/vue_shared/components/gl_modal.vue
'
;
import
{
redirectTo
}
from
'
~/lib/utils/url_utility
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
s__
,
sprintf
}
from
'
~/locale
'
;
import
eventHub
from
'
../event_hub
'
;
export
default
{
...
...
@@ -23,12 +23,26 @@
type
:
String
,
required
:
true
,
},
labelTextColor
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
text
()
{
return
s__
(
`Milestones|Promoting this label will make it available for all projects inside the group.
Existing project labels with the same name will be merged. This action cannot be reversed.`
);
},
title
()
{
const
label
=
`<span
class="label color-label"
style="background-color:
${
this
.
labelColor
}
; color:
${
this
.
labelTextColor
}
;"
>
${
this
.
labelTitle
}
</span>`
;
return
sprintf
(
s__
(
'
Labels|Promote label %{labelTitle} to Group Label?
'
),
{
labelTitle
:
label
,
},
false
);
},
},
methods
:
{
onSubmit
()
{
...
...
@@ -55,15 +69,9 @@
>
<div
slot=
"title"
v-html=
"title"
>
{{
s__
(
'
Labels|Promote label
'
)
}}
<span
class=
"label color-label"
:style=
"
{ backgroundColor: labelColor }"
>
{{
labelTitle
}}
</span>
{{
s__
(
'
Labels|to Group Label?
'
)
}}
{{
title
}}
</div>
{{
text
}}
...
...
app/assets/javascripts/pages/projects/labels/index/index.js
View file @
84e01b3e
...
...
@@ -6,47 +6,52 @@ import PromoteLabelModal from '../components/promote_label_modal.vue';
Vue
.
use
(
Translate
);
const
onRequestFinished
=
({
labelUrl
,
successful
})
=>
{
const
button
=
document
.
querySelector
(
`.js-promote-project-label[data-url="
${
labelUrl
}
"]`
);
const
initLabelIndex
=
()
=>
{
initLabels
();
const
onRequestFinished
=
({
labelUrl
,
successful
})
=>
{
const
button
=
document
.
querySelector
(
`.js-promote-project-label-button[data-url="
${
labelUrl
}
"]`
);
if
(
!
successful
)
{
button
.
removeAttribute
(
'
disabled
'
);
}
};
};
const
onRequestStarted
=
(
labelUrl
)
=>
{
const
button
=
document
.
querySelector
(
`.js-promote-project-label
[data-url="
${
labelUrl
}
"]`
);
const
onRequestStarted
=
(
labelUrl
)
=>
{
const
button
=
document
.
querySelector
(
`.js-promote-project-label-button
[data-url="
${
labelUrl
}
"]`
);
button
.
setAttribute
(
'
disabled
'
,
''
);
eventHub
.
$once
(
'
promoteLabelModal.requestFinished
'
,
onRequestFinished
);
};
};
const
onDeleteButtonClick
=
(
event
)
=>
{
const
onDeleteButtonClick
=
(
event
)
=>
{
const
button
=
event
.
currentTarget
;
const
modalProps
=
{
labelTitle
:
button
.
dataset
.
labelTitle
,
labelColor
:
button
.
dataset
.
labelColor
,
labelTextColor
:
button
.
dataset
.
labelTextColor
,
url
:
button
.
dataset
.
url
,
};
eventHub
.
$once
(
'
promoteLabelModal.requestStarted
'
,
onRequestStarted
);
eventHub
.
$emit
(
'
promoteLabelModal.props
'
,
modalProps
);
};
};
const
promoteLabelButtons
=
document
.
querySelectorAll
(
'
.js-promote-project-label
'
);
promoteLabelButtons
.
forEach
((
button
)
=>
{
const
promoteLabelButtons
=
document
.
querySelectorAll
(
'
.js-promote-project-label-button
'
);
promoteLabelButtons
.
forEach
((
button
)
=>
{
button
.
addEventListener
(
'
click
'
,
onDeleteButtonClick
);
});
});
eventHub
.
$once
(
'
promoteLabelModal.mounted
'
,
()
=>
{
eventHub
.
$once
(
'
promoteLabelModal.mounted
'
,
()
=>
{
promoteLabelButtons
.
forEach
((
button
)
=>
{
button
.
removeAttribute
(
'
disabled
'
);
});
});
});
const
initLabelIndex
=
()
=>
{
initLabels
()
;
const
promoteLabelModal
=
document
.
getElementById
(
'
promote-label-modal
'
);
let
promoteLabelModalComponent
;
const
promoteLabelModalComponent
=
new
Vue
({
el
:
'
#promote-label-modal
'
,
if
(
promoteLabelModal
)
{
promoteLabelModalComponent
=
new
Vue
({
el
:
promoteLabelModal
,
components
:
{
PromoteLabelModal
,
},
...
...
@@ -55,6 +60,7 @@ const initLabelIndex = () => {
modalProps
:
{
labelTitle
:
''
,
labelColor
:
''
,
labelTextColor
:
''
,
url
:
''
,
},
};
...
...
@@ -77,13 +83,9 @@ const initLabelIndex = () => {
});
},
});
const
promoteLabelModal
=
document
.
getElementById
(
'
promote-label-modal
'
);
let
withLabel
;
if
(
promoteLabelModal
!=
null
)
{
withLabel
=
promoteLabelModalComponent
;
}
return
withLabel
;
return
promoteLabelModalComponent
;
};
document
.
addEventListener
(
'
DOMContentLoaded
'
,
initLabelIndex
);
app/assets/stylesheets/framework/modal.scss
View file @
84e01b3e
...
...
@@ -4,13 +4,15 @@
.page-title
,
.modal-title
{
margin-top
:
0
;
.color-label
{
font-size
:
$gl-font-size
;
padding
:
$gl-vert-padding
$label-padding-modal
;
}
}
.page-title
{
margin-top
:
0
;
}
}
.modal-body
{
...
...
app/views/projects/labels/index.html.haml
View file @
84e01b3e
...
...
@@ -3,8 +3,8 @@
-
hide_class
=
''
-
can_admin_label
=
can?
(
current_user
,
:admin_label
,
@project
)
#promote-label-modal
-
if
@labels
.
exists?
||
@prioritized_labels
.
exists?
#promote-label-modal
%div
{
class:
container_class
}
.top-area.adjust
.nav-text
...
...
app/views/projects/milestones/show.html.haml
View file @
84e01b3e
...
...
@@ -27,12 +27,12 @@
Edit
-
if
@project
.
group
%button
.js-promote-project-milestone.btn.btn-grouped
{
data:
{
toggle:
'modal'
,
%button
.js-promote-project-milestone
-button
.btn.btn-grouped
{
data:
{
toggle:
'modal'
,
target:
'#promote-milestone-modal'
,
milestone_title:
@milestone
.
title
,
url:
promote_project_milestone_path
(
@milestone
.
project
,
@milestone
),
container:
'body'
,
disabled:
true
}
}
container:
'body'
}
,
disabled:
true
}
=
_
(
'Promote'
)
#promote-milestone-modal
...
...
app/views/shared/_label.html.haml
View file @
84e01b3e
...
...
@@ -48,10 +48,11 @@
.pull-right.hidden-xs.hidden-sm
-
if
label
.
is_a?
(
ProjectLabel
)
&&
label
.
project
.
group
&&
can?
(
current_user
,
:admin_label
,
label
.
project
.
group
)
%a
.js-promote-project-label.btn.btn-transparent.btn-action.has-tooltip
{
title:
_
(
'Promote to Group Label'
),
%a
.js-promote-project-label
-button
.btn.btn-transparent.btn-action.has-tooltip
{
title:
_
(
'Promote to Group Label'
),
data:
{
url:
promote_project_label_path
(
label
.
project
,
label
),
label_title:
label
.
title
,
label_color:
label
.
color
,
label_text_color:
label
.
text_color
,
target:
'#promote-label-modal'
,
container:
'body'
,
toggle:
'modal'
},
...
...
app/views/shared/milestones/_milestone.html.haml
View file @
84e01b3e
...
...
@@ -51,14 +51,14 @@
\
-
if
@project
.
group
%a
.js-promote-project-milestone.btn.btn-xs.btn-grouped.has-tooltip
{
title:
_
(
'Promote to Group Milestone'
),
%a
.js-promote-project-milestone
-button
.btn.btn-xs.btn-grouped.has-tooltip
{
title:
_
(
'Promote to Group Milestone'
),
data:
{
url:
promote_project_milestone_path
(
milestone
.
project
,
milestone
),
milestone_title:
milestone
.
title
,
target:
'#promote-milestone-modal'
,
container:
'body'
,
toggle:
'modal'
},
disabled:
true
}
Promote
=
_
(
'Promote'
)
=
link_to
'Close Milestone'
,
project_milestone_path
(
@project
,
milestone
,
milestone:
{
state_event: :close
}),
method: :put
,
remote:
true
,
class:
"btn btn-xs btn-close btn-grouped"
...
...
changelogs/unreleased/jivl-new-modal-project-labels-milestones.yml
View file @
84e01b3e
---
title
:
Added
vue based promotion modals for labels and milestone
s
title
:
Added
new design for promotion modal
s
merge_request
:
17197
author
:
type
:
other
spec/javascripts/pages/labels/components/promote_label_modal_spec.js
View file @
84e01b3e
...
...
@@ -7,35 +7,28 @@ import mountComponent from '../../../helpers/vue_mount_component_helper';
describe
(
'
Promote label modal
'
,
()
=>
{
let
vm
;
let
Component
;
const
Component
=
Vue
.
extend
(
promoteLabelModal
)
;
const
labelMockData
=
{
labelTitle
:
'
Documentation
'
,
labelColor
:
'
#5cb85c
'
,
url
:
`
${
gl
.
TEST_HOST
}
/dummy/endpoint`
,
labelTextColor
:
'
#ffffff
'
,
url
:
`
${
gl
.
TEST_HOST
}
/dummy/promote/labels`
,
};
beforeEach
(()
=>
{
Component
=
Vue
.
extend
(
promoteLabelModal
);
});
describe
(
'
Modal title and description
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
Component
,
{
...
labelMockData
,
});
vm
=
mountComponent
(
Component
,
labelMockData
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
should contain
the proper description
'
,
()
=>
{
it
(
'
contains
the proper description
'
,
()
=>
{
expect
(
vm
.
text
).
toContain
(
'
Promoting this label will make it available for all projects inside the group
'
);
expect
(
vm
.
text
).
toContain
(
'
Existing project labels with the same name will be merged
'
);
expect
(
vm
.
text
).
toContain
(
'
This action cannot be reversed.
'
);
});
it
(
'
should contain
a label span with the color
'
,
()
=>
{
it
(
'
contains
a label span with the color
'
,
()
=>
{
const
labelFromTitle
=
vm
.
$el
.
querySelector
(
'
.modal-header .label.color-label
'
);
expect
(
labelFromTitle
.
style
.
backgroundColor
).
not
.
toBe
(
null
);
...
...
@@ -55,7 +48,7 @@ describe('Promote label modal', () => {
vm
.
$destroy
();
});
it
(
'
should redirect
when a label is promoted
'
,
(
done
)
=>
{
it
(
'
redirects
when a label is promoted
'
,
(
done
)
=>
{
const
responseURL
=
`
${
gl
.
TEST_HOST
}
/dummy/endpoint`
;
spyOn
(
axios
,
'
post
'
).
and
.
callFake
((
url
)
=>
{
expect
(
url
).
toBe
(
labelMockData
.
url
);
...
...
@@ -71,6 +64,7 @@ describe('Promote label modal', () => {
vm
.
onSubmit
()
.
then
(()
=>
{
expect
(
redirectSpy
).
toHaveBeenCalledWith
(
responseURL
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
promoteLabelModal.requestFinished
'
,
{
labelUrl
:
labelMockData
.
url
,
successful
:
true
});
})
.
then
(
done
)
.
catch
(
done
.
fail
);
...
...
spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
View file @
84e01b3e
...
...
@@ -7,34 +7,26 @@ import mountComponent from '../../../../helpers/vue_mount_component_helper';
describe
(
'
Promote milestone modal
'
,
()
=>
{
let
vm
;
let
Component
;
const
Component
=
Vue
.
extend
(
promoteMilestoneModal
)
;
const
milestoneMockData
=
{
milestoneTitle
:
'
v1.0
'
,
url
:
`
${
gl
.
TEST_HOST
}
/dummy/
endpoint
`
,
url
:
`
${
gl
.
TEST_HOST
}
/dummy/
promote/milestones
`
,
};
beforeEach
(()
=>
{
Component
=
Vue
.
extend
(
promoteMilestoneModal
);
});
describe
(
'
Modal title and description
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
Component
,
{
...
milestoneMockData
,
});
vm
=
mountComponent
(
Component
,
milestoneMockData
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
should contain
the proper description
'
,
()
=>
{
it
(
'
contains
the proper description
'
,
()
=>
{
expect
(
vm
.
text
).
toContain
(
'
Promoting this milestone will make it available for all projects inside the group.
'
);
expect
(
vm
.
text
).
toContain
(
'
Existing project milestones with the same name will be merged.
'
);
expect
(
vm
.
text
).
toContain
(
'
This action cannot be reversed.
'
);
});
it
(
'
should contain
the correct title
'
,
()
=>
{
it
(
'
contains
the correct title
'
,
()
=>
{
expect
(
vm
.
title
).
toEqual
(
'
Promote v1.0 to group milestone?
'
);
});
});
...
...
@@ -51,7 +43,7 @@ describe('Promote milestone modal', () => {
vm
.
$destroy
();
});
it
(
'
should redirect
when a milestone is promoted
'
,
(
done
)
=>
{
it
(
'
redirects
when a milestone is promoted
'
,
(
done
)
=>
{
const
responseURL
=
`
${
gl
.
TEST_HOST
}
/dummy/endpoint`
;
spyOn
(
axios
,
'
post
'
).
and
.
callFake
((
url
)
=>
{
expect
(
url
).
toBe
(
milestoneMockData
.
url
);
...
...
@@ -67,6 +59,7 @@ describe('Promote milestone modal', () => {
vm
.
onSubmit
()
.
then
(()
=>
{
expect
(
redirectSpy
).
toHaveBeenCalledWith
(
responseURL
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
promoteMilestoneModal.requestFinished
'
,
{
milestoneUrl
:
milestoneMockData
.
url
,
successful
:
true
});
})
.
then
(
done
)
.
catch
(
done
.
fail
);
...
...
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