Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ dist

rdmo/management/static

rdmo/projects/static/projects/js/projects.js
rdmo/projects/static/projects/fonts
rdmo/projects/static/projects/css/projects.css

screenshots
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@uiw/react-codemirror": "^4.19.9",
"bootstrap-sass": "^3.4.1",
"classnames": "^2.3.2",
"date-fns": "^3.6.0",
"font-awesome": "4.7.0",
"jquery": "^3.6.0",
"js-cookie": "^2.2.1",
Expand All @@ -25,9 +26,11 @@
"prop-types": "^15.7.2",
"react": "^18.2.0",
"react-bootstrap": "0.33.1",
"react-datepicker": "6.6.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-dropzone": "^10.0.0",
"react-redux": "^7.2.4",
"react-select": "^5.7.0",
"redux": "^4.1.1",
Expand Down
4 changes: 3 additions & 1 deletion rdmo/accounts/serializers/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ class Meta:
'id',
'groups',
'role',
'memberships'
'memberships',
'is_superuser',
'is_staff'
]
if settings.USER_API:
fields += [
Expand Down
8 changes: 8 additions & 0 deletions rdmo/accounts/viewsets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.contrib.auth import get_user_model

from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet

from django_filters.rest_framework import DjangoFilterBackend
Expand Down Expand Up @@ -40,3 +43,8 @@ def get_queryset(self):
'role__member', 'role__manager',
'role__editor', 'role__reviewer',
'memberships')

@action(detail=False, permission_classes=(IsAuthenticated, ))
def current(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
32 changes: 32 additions & 0 deletions rdmo/core/assets/js/components/FileUploadButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useDropzone } from 'react-dropzone'

const FileUploadButton = ({ acceptedTypes, buttonProps, buttonLabel, onImportFile }) => {
const { getRootProps, getInputProps } = useDropzone({
accept: acceptedTypes,
onDrop: acceptedFiles => {
if (acceptedFiles.length > 0) {
onImportFile(acceptedFiles[0])
}
}
})

return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<button className="btn" {...buttonProps}>
<i className="fa fa-download" aria-hidden="true"></i> {buttonLabel}
</button>
</div>
)
}

FileUploadButton.propTypes = {
acceptedTypes: PropTypes.arrayOf(PropTypes.string),
buttonProps: PropTypes.object,
buttonLabel: PropTypes.string.isRequired,
onImportFile: PropTypes.func.isRequired,
}

export default FileUploadButton
40 changes: 40 additions & 0 deletions rdmo/core/assets/js/components/Modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Modal as BootstrapModal } from 'react-bootstrap'

const Modal = ({ bsSize, buttonLabel, buttonProps, title, show, onClose, onSave, children }) => {
return (
<BootstrapModal bsSize={bsSize} className="element-modal" onHide={onClose} show={show}>
<BootstrapModal.Header closeButton>
<h2 className="modal-title">{title}</h2>
</BootstrapModal.Header>
<BootstrapModal.Body>
{ children }
</BootstrapModal.Body>
<BootstrapModal.Footer>
<button type="button" className="btn btn-default" onClick={onClose}>
{gettext('Close')}
</button>
{ onSave ?
<button type="button" className="btn btn-primary" onClick={onSave} {...buttonProps}>
{buttonLabel ?? gettext('Save')}
</button>
: null
}
</BootstrapModal.Footer>
</BootstrapModal>
)
}

Modal.propTypes = {
bsSize: PropTypes.oneOf(['lg', 'large', 'sm', 'small']),
buttonLabel: PropTypes.string,
buttonProps: PropTypes.object,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
onClose: PropTypes.func.isRequired,
onSave: PropTypes.func,
show: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
}

export default Modal
56 changes: 56 additions & 0 deletions rdmo/core/assets/js/components/SearchField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react'
import PropTypes from 'prop-types'

const SearchField = ({ value, onChange, onSearch, placeholder }) => {

const handleSearch = () => {
onSearch(value)
}

const handleChange = (newValue) => {
onChange(newValue)
}

const handleButtonClick = () => {
onChange('')
handleSearch()
}

const handleKeyDown = (event) => {
if (event.key === 'Enter') {
handleSearch()
}
}

return (
<div className="form-group mb-0">
<div className="input-group">
<input
type="text"
className="form-control"
placeholder={placeholder}
value={value}
onChange={(e) => handleChange(e.target.value)}
onKeyDown={handleKeyDown}
/>
<span className="input-group-btn">
<button className="btn btn-default" onClick={handleButtonClick}>
<span className="fa fa-times"></span>
</button>
<button className="btn btn-primary" onClick={handleSearch}>
{gettext('Search')}
</button>
</span>
</div>
</div>
)
}

SearchField.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
onSearch: PropTypes.func.isRequired,
placeholder: PropTypes.string.isRequired,
}

export default SearchField
38 changes: 38 additions & 0 deletions rdmo/core/assets/js/components/Select.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactSelect from 'react-select'

const Select = ({ options, onChange, placeholder, value }) => {
const selectedOption = options.find(option => option.value === value) || null
const handleChange = (selected) => {
onChange(selected ? selected.value : null)
}

return (
<div className="form-group mb-0">
<ReactSelect
className="react-select"
classNamePrefix="react-select"
options={options}
onChange={handleChange}
value={selectedOption}
isClearable
placeholder={placeholder}
/>
</div>
)
}

Select.propTypes = {
value: PropTypes.string,
options: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.string.isRequired,
label: PropTypes.string.isRequired
})
).isRequired,
onChange: PropTypes.func,
placeholder: PropTypes.string
}

export default Select
40 changes: 40 additions & 0 deletions rdmo/core/assets/js/components/UploadDropZone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { useDropzone } from 'react-dropzone'

const UploadDropZone = ({ acceptedTypes, onImportFile }) => {
const [errorMessage, setErrorMessage] = useState('')

const { getRootProps, getInputProps } = useDropzone({
accept: acceptedTypes,
onDropAccepted: acceptedFiles => {
if (acceptedFiles.length > 0) {
onImportFile(acceptedFiles[0])
setErrorMessage('')
}
},
onDropRejected: rejectedFiles => {
console.log(rejectedFiles)
setErrorMessage(interpolate(gettext('%s has unsupported file type'), [rejectedFiles[0].path]))
}
})

return (
<section className="dropzone-container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p className="mb-0">
{gettext('Drag and drop a file here or click to select a file')}
</p>
{errorMessage && <div className="alert alert-danger mt-2">{errorMessage}</div>}
</div>
</section>
)
}

UploadDropZone.propTypes = {
acceptedTypes: PropTypes.arrayOf(PropTypes.string),
onImportFile: PropTypes.func.isRequired,
}

export default UploadDropZone
7 changes: 7 additions & 0 deletions rdmo/core/assets/js/components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { default as FileUploadButton } from './FileUploadButton'
export { default as Link } from './Link'
export { default as LinkButton } from './LinkButton'
export { default as Modal } from './Modal'
export { default as UploadDropZone } from './UploadDropZone'
export { default as SearchField } from './SearchField'
export { default as Select } from './Select'
3 changes: 3 additions & 0 deletions rdmo/core/assets/js/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as useFormattedDateTime } from './useFormattedDateTime'
export { default as useModal } from './useModal'
export { default as useScrollToTop } from './useScrollToTop'
20 changes: 20 additions & 0 deletions rdmo/core/assets/js/hooks/useFormattedDateTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { format } from 'date-fns'
import { de, enUS } from 'date-fns/locale'

const getLocaleObject = (language) => {
return language === 'de' ? de : enUS
}

const FORMAT_STRINGS = {
en: 'MMM d, yyyy, h:mm a',
de: 'd. MMM yyyy, H:mm',
}

export const useFormattedDateTime = (date, language) => {
const locale = getLocaleObject(language)
const formatString = language === 'de' ? FORMAT_STRINGS.de : FORMAT_STRINGS.en

return format(new Date(date), formatString, { locale })
}

export default useFormattedDateTime
11 changes: 11 additions & 0 deletions rdmo/core/assets/js/hooks/useModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useState } from 'react'

const useModal = () => {
const [show, setShow] = useState(false)
const open = () => setShow(true)
const close = () => setShow(false)

return {show, open, close}
}

export default useModal
22 changes: 22 additions & 0 deletions rdmo/core/assets/js/hooks/useScrollToTop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useState, useEffect } from 'react'

const useScrollToTop = () => {
const [showTopButton, setShowTopButton] = useState(false)

useEffect(() => {
const handleScroll = () => {
setShowTopButton(window.pageYOffset > 100)
}

window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])

const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}

return { showTopButton, scrollToTop }
}

export default useScrollToTop
15 changes: 15 additions & 0 deletions rdmo/core/assets/js/utils/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const encodeParams = params => {
return Object.entries(params).map(item => {
const [key, value] = item

if (Array.isArray(value)) {
return value.map(v => {
return encodeURIComponent(key) + '=' + encodeURIComponent(v)
}).join('&')
} else {
return encodeURIComponent(key) + '=' + encodeURIComponent(value)
}
}).join('&')
}

export { encodeParams }
5 changes: 5 additions & 0 deletions rdmo/core/assets/js/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './api'
export { default as baseUrl } from './baseUrl'
export { default as language } from './language'
export { default as siteId } from './siteId'
export { default as staticUrl } from './staticUrl'
2 changes: 2 additions & 0 deletions rdmo/core/assets/js/utils/language.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// take the language from the <head> of the django template
export default document.querySelector('meta[name="language"]').content
2 changes: 2 additions & 0 deletions rdmo/core/assets/js/utils/siteId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// take the site_id from the <head> of the django template
export default Number(document.querySelector('meta[name="site_id"]').content)
2 changes: 2 additions & 0 deletions rdmo/core/assets/js/utils/staticUrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// take the staticurl from the <head> of the django template
export default document.querySelector('meta[name="staticurl"]').content.replace(/\/+$/, '')
4 changes: 4 additions & 0 deletions rdmo/core/templates/core/base_head.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{% load static %}
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}

<title>{{ request.site.name }}</title>

<meta charset="UTF-8" />
Expand All @@ -10,5 +13,6 @@
<meta name='baseurl' content="{% url 'home' %}" />
<meta name='staticurl' content="{% static '' %}" />
<meta name='site_id' content="{{ settings.SITE_ID }}" />
<meta name='language' content="{{ LANGUAGE_CODE }}">

<link rel="shortcut icon" href="{% static 'core/img/favicon.png' %}" type="image/png" />
13 changes: 13 additions & 0 deletions rdmo/core/templates/core/projects_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends 'core/base.html' %}

{% block content %}

<div class="container">
<div class="row">
<div class="page col-md-12">
{% block page %}{% endblock %}
</div>
</div>
</div>

{% endblock %}
Binary file modified rdmo/locale/de/LC_MESSAGES/djangojs.mo
Binary file not shown.
Loading