Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Zhige Yu
scientia
Commits
ec1dfac0
Commit
ec1dfac0
authored
Oct 23, 2020
by
GaryGao
Browse files
Merge remote-tracking branch 'origin/production' into testing_features
parents
807d6216
f565a7ee
Pipeline
#160041
canceled with stages
Changes
15
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
src/components/atoms/FileExtensionIcon/index.tsx
0 → 100644
View file @
ec1dfac0
import
React
,
{
useEffect
,
useState
}
from
'
react
'
import
{
faFileAlt
,
faFilePdf
,
faLink
,
faFileCode
,
faFileExcel
,
faFilePowerpoint
,
faFileVideo
,
faFileWord
,
IconDefinition
,
faFile
}
from
'
@fortawesome/free-solid-svg-icons
'
import
{
EnumDictionary
}
from
'
constants/types
'
import
{
FontAwesomeIcon
}
from
'
@fortawesome/react-fontawesome
'
enum
FileCategory
{
CODE
=
"
code
"
,
EXCEL
=
"
excel
"
,
LINK
=
"
link
"
,
PDF
=
"
pdf
"
,
PLAIN_TEXT
=
"
plain text
"
,
POWERPOINT
=
"
powerpoint
"
,
VIDEO
=
"
video
"
,
WORD
=
"
word
"
,
OTHER
=
"
other
"
,
}
const
categoryExtensions
:
EnumDictionary
<
FileCategory
,
string
[]
>
=
{
[
FileCategory
.
CODE
]:
[
"
c
"
,
"
cpp
"
,
"
h
"
,
"
hpp
"
,
"
java
"
,
"
kt
"
,
"
hs
"
,
"
lhs
"
,
"
sh
"
,
"
py
"
,
],
[
FileCategory
.
EXCEL
]:
[
"
xls
"
,
"
xlsx
"
],
[
FileCategory
.
LINK
]:
[],
[
FileCategory
.
PDF
]:
[
"
pdf
"
],
[
FileCategory
.
POWERPOINT
]:
[
"
ppt
"
,
"
pptx
"
],
[
FileCategory
.
PLAIN_TEXT
]:
[
"
txt
"
,
""
],
[
FileCategory
.
VIDEO
]:
[
"
flv
"
,
"
avi
"
,
"
mp4
"
],
[
FileCategory
.
WORD
]:
[
"
doc
"
,
"
docx
"
],
[
FileCategory
.
OTHER
]:
[],
}
const
categoryIcons
:
EnumDictionary
<
FileCategory
,
IconDefinition
>
=
{
[
FileCategory
.
CODE
]:
faFileCode
,
[
FileCategory
.
EXCEL
]:
faFileExcel
,
[
FileCategory
.
LINK
]:
faLink
,
[
FileCategory
.
PDF
]:
faFilePdf
,
[
FileCategory
.
POWERPOINT
]:
faFilePowerpoint
,
[
FileCategory
.
PLAIN_TEXT
]:
faFileAlt
,
[
FileCategory
.
VIDEO
]:
faFileVideo
,
[
FileCategory
.
WORD
]:
faFileWord
,
[
FileCategory
.
OTHER
]:
faFile
,
}
const
extensionDictionary
:
{
[
suffix
:
string
]:
FileCategory
}
=
(()
=>
{
const
dic
:
{
[
suffix
:
string
]:
FileCategory
}
=
{}
for
(
const
key
in
categoryExtensions
)
{
const
value
=
categoryExtensions
[
key
as
FileCategory
]
value
.
forEach
(
extension
=>
dic
[
extension
]
=
key
as
FileCategory
)
}
return
dic
})()
interface
Props
{
suffixes
:
string
[]
style
?:
React
.
CSSProperties
onClick
?:
((
event
:
React
.
MouseEvent
)
=>
void
)
}
const
FileExtensionIcon
:
React
.
FC
<
Props
>
=
({
suffixes
,
style
,
onClick
,
})
=>
{
const
[
category
,
setCategory
]
=
useState
<
FileCategory
>
(
FileCategory
.
OTHER
)
// Set icon as per suffixes' categories
useEffect
(()
=>
{
let
category
:
FileCategory
|
undefined
=
undefined
for
(
const
suffix
of
suffixes
)
{
const
currentCategory
=
extensionDictionary
[
suffix
]
??
FileCategory
.
OTHER
if
(
category
&&
(
category
!==
currentCategory
))
{
setCategory
(
FileCategory
.
OTHER
)
return
}
category
=
currentCategory
}
setCategory
(
category
??
FileCategory
.
OTHER
)
},
[
suffixes
])
return
(
<
FontAwesomeIcon
style
=
{
style
}
icon
=
{
categoryIcons
[
category
]
}
onClick
=
{
e
=>
{
e
.
stopPropagation
()
onClick
?.(
e
)
}
}
fixedWidth
/>
)
}
export
default
FileExtensionIcon
\ No newline at end of file
src/components/groups/SideBarOutlineGroup/index.tsx
View file @
ec1dfac0
...
...
@@ -14,7 +14,7 @@ const SideBarOutlineGroup: React.FC = () => {
let
outlineButtons
=
[
{
title
:
"
Overview
"
,
title
:
"
Dashboard
"
,
activeURL
:
`/modules/
${
id
}
/dashboard`
,
icon
:
faHome
,
},
...
...
src/components/modals/EventModal/SubmissionSection/SubmissionFileUpload/index.tsx
0 → 100644
View file @
ec1dfac0
import
React
,
{
useRef
,
useState
}
from
'
react
'
import
styles
from
'
./style.module.scss
'
import
FileItemRow
from
'
components/rows/FileItemRow
'
import
{
Resource
,
ResourceUploadRequirement
,
ResourceUploadStatus
}
from
'
constants/types
'
import
UploadResourceItemRow
from
'
components/rows/UploadResourceItemRow
'
import
{
faDownload
,
faTrash
,
faUpload
}
from
'
@fortawesome/free-solid-svg-icons
'
interface
Props
{
requiredResources
:
ResourceUploadRequirement
[]
uploadedResources
:
ResourceUploadStatus
[]
uploadFile
:
(
file
:
File
,
index
:
number
)
=>
void
}
const
SubmissionFileUpload
:
React
.
FC
<
Props
>
=
({
requiredResources
,
uploadedResources
,
uploadFile
,
})
=>
{
const
uploadRef
=
useRef
<
HTMLInputElement
>
(
null
)
const
[
uploadIndex
,
setUploadIndex
]
=
useState
<
number
>
(
0
)
const
onFileSelection
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
file
=
e
.
target
.
files
?.[
0
]
file
&&
uploadFile
(
file
,
uploadIndex
)
}
const
clickUpload
=
(
index
:
number
)
=>
{
setUploadIndex
(
index
)
uploadRef
.
current
?.
click
()
}
return
(
<
div
>
<
span
>
Requirements:
</
span
>
<
input
type
=
"file"
ref
=
{
uploadRef
}
onChange
=
{
e
=>
onFileSelection
(
e
)
}
style
=
{
{
display
:
"
none
"
}
}
></
input
>
{
requiredResources
.
map
(({
title
,
allowedSuffixes
,
},
index
)
=>
{
return
(
<
div
>
<
UploadResourceItemRow
title
=
{
title
}
suffixes
=
{
allowedSuffixes
}
colour
=
"pink"
respondingIcons
=
{
[
[
faUpload
,
_
=>
clickUpload
(
index
)],
]
}
/>
</
div
>
)
})
}
<
span
>
Uploaded resources:
</
span
>
{
uploadedResources
.
map
(({
title
,
suffix
,
})
=>
(
<
UploadResourceItemRow
title
=
{
title
}
suffixes
=
{
[
suffix
]
}
colour
=
"teal"
respondingIcons
=
{
[
[
faDownload
,
e
=>
alert
(
"
downloading
"
)],
[
faTrash
,
e
=>
alert
(
"
deleting
"
)],
]
}
/>
))
}
</
div
>
)
}
export
default
SubmissionFileUpload
\ No newline at end of file
src/components/modals/EventModal/SubmissionSection/SubmissionFileUpload/style.module.scss
0 → 100644
View file @
ec1dfac0
src/components/modals/EventModal/SubmissionSection/index.tsx
0 → 100644
View file @
ec1dfac0
import
React
,
{
useState
}
from
"
react
"
import
Button
from
"
react-bootstrap/Button
"
import
styles
from
"
./style.module.scss
"
import
{
EnumDictionary
,
ResourceUploadRequirement
,
ResourceUploadStatus
,
TimelineEvent
}
from
"
constants/types
"
import
ButtonGroup
from
"
react-bootstrap/esm/ButtonGroup
"
import
Form
from
"
react-bootstrap/Form
"
import
Col
from
"
react-bootstrap/Col
"
import
SubmissionFileUpload
from
"
./SubmissionFileUpload
"
enum
Stage
{
DECLARATION
=
"
Declaration
"
,
GROUP_FORMATION
=
"
Group Formation
"
,
FILE_UPLOAD
=
"
File Upload
"
,
}
const
allStages
:
Stage
[]
=
[
Stage
.
DECLARATION
,
Stage
.
GROUP_FORMATION
,
Stage
.
FILE_UPLOAD
]
interface
SubmitDeclarationProps
{
}
const
SubmitDeclarationSection
:
React
.
FC
<
SubmitDeclarationProps
>
=
({})
=>
{
return
(
<
Form
>
<
Form
.
Group
className
=
{
styles
.
submitDeclParagraph
}
>
<
Form
.
Label
>
We declare that this final submitted version is our unaided work.
</
Form
.
Label
>
<
Form
.
Label
>
We acknowledge the following people for help through our original discussions:
</
Form
.
Label
>
</
Form
.
Group
>
<
Form
.
Group
>
<
Form
.
Row
>
<
Col
>
<
Form
.
Label
className
=
{
styles
.
submitDeclTitle
}
>
Name
</
Form
.
Label
>
<
Form
.
Control
placeholder
=
"Name"
/>
<
Form
.
Control
placeholder
=
"Name"
/>
<
Form
.
Control
placeholder
=
"Name"
/>
<
Form
.
Control
placeholder
=
"Name"
/>
<
Form
.
Control
placeholder
=
"Name"
/>
</
Col
>
<
Col
>
<
Form
.
Label
className
=
{
styles
.
submitDeclTitle
}
>
Login (if member of DOC)
</
Form
.
Label
>
<
Form
.
Control
placeholder
=
"Login"
/>
<
Form
.
Control
placeholder
=
"Login"
/>
<
Form
.
Control
placeholder
=
"Login"
/>
<
Form
.
Control
placeholder
=
"Login"
/>
<
Form
.
Control
placeholder
=
"Login"
/>
</
Col
>
</
Form
.
Row
>
<
Form
.
Row
>
<
Col
>
<
Button
variant
=
"secondary"
className
=
{
styles
.
submitDeclButton
}
>
Change Declaration
</
Button
>
</
Col
>
<
Col
>
<
Button
variant
=
"secondary"
className
=
{
styles
.
submitDeclButton
}
>
Reset Form
</
Button
>
</
Col
>
</
Form
.
Row
>
</
Form
.
Group
>
<
Form
.
Group
className
=
{
styles
.
submitDeclParagraph
}
>
<
p
>
Fill in or modify these boxes as appropriate.
</
p
>
<
p
>
Names must contain only alphabetic characters [A-Za-z],
spaces and hyphens (-); logins must contain only lower case
letters and/or digits [a-z0-9]. Name-login pairs with incorrect
syntax will be ignored.
</
p
>
</
Form
.
Group
>
<
Form
.
Group
>
<
Form
.
Row
>
<
Col
>
<
Button
variant
=
"secondary"
className
=
{
styles
.
submitDeclButton
}
>
Delete Declaration
</
Button
>
</
Col
>
<
Col
>
<
p
>
Only possible when no submission record exists
(neither hardcopy nor electronic).
</
p
>
</
Col
>
</
Form
.
Row
>
</
Form
.
Group
>
</
Form
>
)
}
interface
Props
{
event
?:
TimelineEvent
activeDay
:
Date
}
const
SubmissionSection
:
React
.
FC
<
Props
>
=
({
event
,
activeDay
,
})
=>
{
const
[
stage
,
setStage
]
=
useState
(
Stage
.
DECLARATION
)
const
[
requirements
,
setRequirements
]
=
useState
<
ResourceUploadRequirement
[]
>
(
dummyRUR
)
const
[
uploaded
,
setUploaded
]
=
useState
<
ResourceUploadStatus
[]
>
([])
const
uploadFile
=
(
file
:
File
,
index
:
number
)
=>
{
const
requirement
=
requirements
[
index
]
const
suffix
=
file
.
name
.
substr
(
file
.
name
.
lastIndexOf
(
'
.
'
)
+
1
)
console
.
log
(
suffix
);
if
(
requirement
.
allowedSuffixes
.
includes
(
suffix
))
{
const
title
=
requirement
.
title
const
newFile
=
new
File
([
file
],
`
${
title
}${
suffix
===
""
?
""
:
`.
${
suffix
}
`
}
`
,
{
type
:
file
.
type
})
const
newStatus
:
ResourceUploadStatus
=
{
file
:
newFile
,
title
:
title
,
suffix
:
suffix
,
timestamp
:
new
Date
(),
oldRequirement
:
requirement
}
console
.
log
(
newFile
);
setRequirements
(
requirements
.
filter
((
_
,
i
)
=>
i
!==
index
))
setUploaded
([...
uploaded
,
newStatus
])
}
else
{
alert
(
"
u kidding?
"
)
}
}
const
removeFile
=
(
index
:
number
)
=>
{
const
status
=
uploaded
[
index
]
setRequirements
([...
requirements
,
status
.
oldRequirement
])
setUploaded
(
uploaded
.
filter
((
_
,
i
)
=>
i
!==
index
))
}
const
mainSectionDic
:
EnumDictionary
<
Stage
,
JSX
.
Element
>
=
{
[
Stage
.
DECLARATION
]:
<
SubmitDeclarationSection
/>
,
[
Stage
.
GROUP_FORMATION
]:
<>
</>,
[
Stage
.
FILE_UPLOAD
]:
(
<
SubmissionFileUpload
requiredResources
=
{
requirements
}
uploadedResources
=
{
uploaded
}
uploadFile
=
{
uploadFile
}
/>
),
}
const
buttonOf
=
(
s
:
Stage
)
=>
{
return
(
<
Button
className
=
{
styles
.
sectionButton
}
onClick
=
{
()
=>
setStage
(
s
)
}
active
=
{
s
===
stage
}
>
{
s
}
</
Button
>
)
}
return
(
<>
<
div
className
=
{
styles
.
sectionSwitcher
}
>
<
ButtonGroup
>
{
allStages
.
map
(
buttonOf
)
}
</
ButtonGroup
>
</
div
>
{
mainSectionDic
[
stage
]
}
</>
)
}
const
dummyRUR
:
ResourceUploadRequirement
[]
=
[
{
title
:
"
foo
"
,
allowedSuffixes
:
[
"
pdf
"
]
},
{
title
:
"
report
"
,
allowedSuffixes
:
[
"
pdf
"
,
"
txt
"
,
"
pptx
"
,
"
ppt
"
]
},
{
title
:
"
HaskellCoursework
"
,
allowedSuffixes
:
[
"
hs
"
,
"
lhs
"
]
},
{
title
:
"
allow_empty_suffix
"
,
allowedSuffixes
:
[
""
,
"
txt
"
]
},
]
export
default
SubmissionSection
src/components/modals/EventModal/SubmissionSection/style.module.scss
0 → 100644
View file @
ec1dfac0
.sectionButton
{
background-color
:
var
(
--
primary-button
);
color
:
var
(
--
primary-button-text
);
border-width
:
0rem
;
border-radius
:
0
.5rem
;
margin
:
0
;
justify-content
:
space-between
;
height
:
2
.25rem
;
font-size
:
1
.05rem
;
transition
:
0
.2s
background-color
;
-webkit-transition
:
0
.2s
background-color
;
-moz-transition
:
0
.2s
background-color
;
}
.sectionButton
:active
,
.sectionButton
:global
(
.active
)
{
background
:
var
(
--
primary-button-active
)
!
important
;
color
:
var
(
--
primary-button-text-active
)
!
important
;
font-weight
:
500
;
border-width
:
0rem
;
height
:
2
.25rem
;
line-height
:
1
.375rem
;
box-shadow
:
none
!
important
;
}
.sectionButton
:hover
{
background-color
:
var
(
--
primary-button-hover
);
color
:
var
(
--
primary-button-text
);
box-shadow
:
none
!
important
;
}
.sectionButton
:focus
{
outline
:
none
!
important
;
outline-offset
:
none
!
important
;
box-shadow
:
none
!
important
;
}
.sectionSwitcher
{
top
:
0
;
left
:
0
;
position
:
sticky
;
z-index
:
5
;
background
:
var
(
--
background-color
);
height
:
2
.5rem
!
important
;
}
.sectionSwitcher
>
*
{
width
:
100%
;
}
.submitDeclButton.focus
,
.submitDeclButton
:focus
{
background
:
var
(
--
primary-button
);
color
:
var
(
--
primary-button-text
);
box-shadow
:
none
;
}
.submitDeclButton
{
color
:
var
(
--
primary-button-text
);
background
:
var
(
--
primary-button
);
font-weight
:
500
;
letter-spacing
:
0
;
border-width
:
0rem
;
line-height
:
1
.375rem
;
height
:
2
.25rem
;
border-radius
:
0
.5rem
!
important
;
transition
:
0
.2s
background
;
-webkit-transition
:
0
.2s
background
;
-moz-transition
:
0
.2s
background
;
text-align
:
center
;
margin
:
0
;
width
:
100%
;
}
.submitDeclButton.active
,
.submitDeclButton
:active
{
background
:
var
(
--
primary-button-active
)
!
important
;
color
:
var
(
--
primary-button-text-active
)
!
important
;
font-weight
:
500
;
border-width
:
0rem
;
height
:
2
.25rem
;
line-height
:
1
.375rem
;
}
.submitDeclButton
:hover
{
background-color
:
var
(
--
primary-button-hover
);
color
:
var
(
--
primary-button-text
);
box-shadow
:
none
!
important
;
}
src/components/modals/EventModal/index.tsx
View file @
ec1dfac0
import
React
from
"
react
"
import
React
,
{
useState
}
from
"
react
"
import
Modal
from
"
react-bootstrap/Modal
"
import
Button
from
"
react-bootstrap/Button
"
import
styles
from
"
./style.module.scss
"
import
{
FontAwesomeIcon
}
from
"
@fortawesome/react-fontawesome
"
import
{
FontAwesomeIcon
}
from
"
@fortawesome/react-fontawesome
"
import
{
faTimes
,
faEnvelope
,
faBullhorn
,
faExclamationCircle
,
faCheckCircle
,
faEnvelope
,
faExclamationCircle
,
faTimes
,
IconDefinition
,
}
from
"
@fortawesome/free-solid-svg-icons
"
import
FileItemRow
from
"
components/rows/FileItemRow
"
import
{
resourceTypeToIcon
}
from
"
components/pages/modulePages/ModuleResources/utils
"
import
{
TimelineEvent
}
from
"
constants/types
"
import
{
toDayCount
,
toEventDateTime
}
from
"
utils/functions
"
import
{
Link
,
NavLink
}
from
"
react-router-dom
"
import
{
resourceTypeToIcon
}
from
"
components/pages/modulePages/ModuleResources/utils
"
import
{
TimelineEvent
}
from
"
constants/types
"
import
{
toDayCount
,
toEventDateTime
}
from
"
utils/functions
"
import
SubmissionSection
from
"
./SubmissionSection
"
interface
Props
{
event
?:
TimelineEvent
show
:
boolean
onHide
:
any
onHide
:
()
=>
void
activeDay
:
Date
}
const
EventModal
:
React
.
FC
<
Props
>
=
({
event
,
show
,
onHide
,
activeDay
})
=>
{
const
EventModal
:
React
.
FC
<
Props
>
=
({
event
,
show
,
onHide
,
activeDay
})
=>
{
const
[
viewSubmission
,
setViewSubmission
]
=
useState
<
boolean
>
(
false
);
if
(
!
event
)
return
null
const
timeLeft
=
toDayCount
(
event
.
endDate
)
-
toDayCount
(
activeDay
)
let
assessmentStyle
=
styles
.
blueCard
let
icon
=
undefined
let
icon
:
IconDefinition
|
undefined
=
undefined
let
borderColour
=
"
transparent
"
let
displayText
:
string
switch
(
event
.
assessment
)
{
...
...
@@ -68,78 +73,112 @@ const EventModal: React.FC<Props> = ({ event, show, onHide, activeDay }) => {
icon
=
faCheckCircle
break
}
const
ModalInfo
=
(
<>
{
dummy
.
map
(({
title
,
type
,
tags
,
id
}:
any
)
=>
(
<
FileItemRow
title
=
{
title
}
tags
=
{
tags
}
icon
=
{
resourceTypeToIcon
(
type
)
}
onClick
=
{
()
=>
{
}
}
key
=
{
id
}
/>
))
}
</>
)
const
footerAttributes
=
(():
[
string
,
()
=>
void
]
|
undefined
=>
{
if
(
viewSubmission
)
{
return
[
"
Back
"
,
()
=>
setViewSubmission
(
false
)]
}
if
(
event
.
status
!==
"
unreleased
"
&&
timeLeft
>=
-
1
)
{
return
[
"
Submit
"
,
()
=>
setViewSubmission
(
true
)]
}
return
undefined
})()
const
mainSection
=
viewSubmission
?
(