diff --git a/herreweb_backend/herreweb_backend/settings.py b/herreweb_backend/herreweb_backend/settings.py index 7a0eb53..cd45a28 100644 --- a/herreweb_backend/herreweb_backend/settings.py +++ b/herreweb_backend/herreweb_backend/settings.py @@ -27,9 +27,7 @@ DEBUG = True ALLOWED_HOSTS = [] -CORS_ORIGIN_WHITELIST = [ - 'http://localhost:3000', -] + # Application definition @@ -40,11 +38,13 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'project', 'corsheaders', 'rest_framework', ] MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -104,6 +104,9 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +CORS_ORIGIN_WHITELIST = [ + 'http://localhost:3000', +] # Internationalization # https://docs.djangoproject.com/en/4.1/topics/i18n/ diff --git a/herreweb_backend/herreweb_backend/urls.py b/herreweb_backend/herreweb_backend/urls.py index f58338e..fa6eaa0 100644 --- a/herreweb_backend/herreweb_backend/urls.py +++ b/herreweb_backend/herreweb_backend/urls.py @@ -14,8 +14,14 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include +from rest_framework import routers +from project import views + +router = routers.DefaultRouter() +router.register('projects', views.ProjectView, 'projects') urlpatterns = [ path('admin/', admin.site.urls), + path('api/', include(router.urls)) ] diff --git a/herreweb_backend/herreweb_backend/utils.py b/herreweb_backend/herreweb_backend/utils.py new file mode 100644 index 0000000..ee20d03 --- /dev/null +++ b/herreweb_backend/herreweb_backend/utils.py @@ -0,0 +1,5 @@ +import enum + +class ProjectStatus(enum.Enum): + RUNNING = 1 + ENDED = 2 \ No newline at end of file diff --git a/herreweb_backend/project/admin.py b/herreweb_backend/project/admin.py new file mode 100644 index 0000000..4a7959d --- /dev/null +++ b/herreweb_backend/project/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin +from .models import Project + +class ProjectAdmin(admin.ModelAdmin): + list = ('title', 'description', 'visible') + +# Register projects +admin.site.register(Project, ProjectAdmin) diff --git a/herreweb_backend/project/migrations/0001_initial.py b/herreweb_backend/project/migrations/0001_initial.py new file mode 100644 index 0000000..b034513 --- /dev/null +++ b/herreweb_backend/project/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 4.1.4 on 2022-12-28 15:11 + +from django.db import migrations, models +import herreweb_backend.utils + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Project', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=100)), + ('description', models.TextField(default='No description added')), + ('status', models.IntegerField(default=herreweb_backend.utils.ProjectStatus['RUNNING'])), + ('image', models.ImageField(null=True, upload_to='')), + ('rtd_url', models.CharField(max_length=200, null=True)), + ('url', models.CharField(max_length=200, null=True)), + ('visible', models.BooleanField(default=False)), + ], + ), + ] diff --git a/herreweb_backend/project/migrations/__init__.py b/herreweb_backend/project/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/herreweb_backend/project/models.py b/herreweb_backend/project/models.py new file mode 100644 index 0000000..33de7bd --- /dev/null +++ b/herreweb_backend/project/models.py @@ -0,0 +1,14 @@ +from django.db import models +from herreweb_backend.utils import * + +class Project(models.Model): + title = models.CharField(max_length=100) + description = models.TextField(default="No description added") + status = models.IntegerField(default=ProjectStatus.RUNNING) + image = models.ImageField(null=True) + rtd_url = models.CharField(max_length=200, null=True) + url = models.CharField(max_length=200, null=True) + visible = models.BooleanField(default=False) + + def _str_(self): + return self.title diff --git a/herreweb_backend/project/serializers.py b/herreweb_backend/project/serializers.py new file mode 100644 index 0000000..30a56bf --- /dev/null +++ b/herreweb_backend/project/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import Project + +class ProjectSerializer(serializers.ModelSerializer): + class Meta: + model = Project + fields = ('id' ,'title', 'description', 'status') \ No newline at end of file diff --git a/herreweb_backend/project/views.py b/herreweb_backend/project/views.py new file mode 100644 index 0000000..a384dfa --- /dev/null +++ b/herreweb_backend/project/views.py @@ -0,0 +1,8 @@ +from django.shortcuts import render +from rest_framework import viewsets +from project.serializers import ProjectSerializer +from .models import Project + +class ProjectView(viewsets.ModelViewSet): + serializer_class = ProjectSerializer + queryset = Project.objects.all() diff --git a/herreweb_frontend/.vscode/launch.json b/herreweb_frontend/.vscode/launch.json new file mode 100644 index 0000000..7fb8ffa --- /dev/null +++ b/herreweb_frontend/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/src/Projects.js" + } + ] +} \ No newline at end of file diff --git a/herreweb_frontend/package-lock.json b/herreweb_frontend/package-lock.json index 6d28cf8..8eab401 100644 --- a/herreweb_frontend/package-lock.json +++ b/herreweb_frontend/package-lock.json @@ -14,6 +14,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "react-transition-group": "^4.4.5", "web-vitals": "^2.1.4" } }, @@ -6589,6 +6590,15 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -14249,6 +14259,21 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -21690,6 +21715,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -27049,6 +27083,17 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/herreweb_frontend/package.json b/herreweb_frontend/package.json index 85bb03f..51155c5 100644 --- a/herreweb_frontend/package.json +++ b/herreweb_frontend/package.json @@ -9,6 +9,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "react-transition-group": "^4.4.5", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/herreweb_frontend/src/App.js b/herreweb_frontend/src/App.js deleted file mode 100644 index 3784575..0000000 --- a/herreweb_frontend/src/App.js +++ /dev/null @@ -1,25 +0,0 @@ -import logo from './logo.svg'; -import './App.css'; - -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} - -export default App; diff --git a/herreweb_frontend/src/App.test.js b/herreweb_frontend/src/App.test.js deleted file mode 100644 index 1f03afe..0000000 --- a/herreweb_frontend/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/herreweb_frontend/src/components/App.js b/herreweb_frontend/src/components/App.js new file mode 100644 index 0000000..24a6b4d --- /dev/null +++ b/herreweb_frontend/src/components/App.js @@ -0,0 +1,15 @@ +import logo from '../static/logo.svg'; +import '../static/App.css'; +import Projects from './Projects'; +import AppHeader from './AppHeader'; + +function App() { + return ( +
+ + +
+ ); +} + +export default App; diff --git a/herreweb_frontend/src/components/AppHeader.js b/herreweb_frontend/src/components/AppHeader.js new file mode 100644 index 0000000..60829ab --- /dev/null +++ b/herreweb_frontend/src/components/AppHeader.js @@ -0,0 +1,53 @@ +/* eslint-disable jsx-a11y/accessible-emoji */ +import React, { useState, useEffect } from "react"; +import "../static/AppHeader.css"; +import { CSSTransition } from "react-transition-group"; +import logo from '../static/logo.svg'; + +export default function AppHeader() { + const [isNavVisible, setNavVisibility] = useState(false); + const [isSmallScreen, setIsSmallScreen] = useState(false); + + useEffect(() => { + const mediaQuery = window.matchMedia("(max-width: 700px)"); + mediaQuery.addListener(handleMediaQueryChange); + handleMediaQueryChange(mediaQuery); + + return () => { + mediaQuery.removeListener(handleMediaQueryChange); + }; + }, []); + + const handleMediaQueryChange = mediaQuery => { + if (mediaQuery.matches) { + setIsSmallScreen(true); + } else { + setIsSmallScreen(false); + } + }; + + const toggleNav = () => { + setNavVisibility(!isNavVisible); + }; + + return ( +
+ logo + + + + +
+ ); +} diff --git a/herreweb_frontend/src/components/Projects.js b/herreweb_frontend/src/components/Projects.js new file mode 100644 index 0000000..3f5191e --- /dev/null +++ b/herreweb_frontend/src/components/Projects.js @@ -0,0 +1,65 @@ +import React, { Component } from "react" + +const projectItems = [ + { + id: 1, + title: "Thelendar", + description: "Stronghold Kingdoms", + completed: 1 + }, + + { + id: 2, + title: "Ghidra Assistant", + description: "Reversing tools for Ghidra", + completed: 0 + }, + + { + id: 3, + title: "Kerk Tallies", + description: "Creating Tally lights for Church", + completed: 0 + }, +]; + +class Projects extends Component { + constructor(props) { + super(props); + this.state = { projectItems } + }; + + async componentDidMount() { + this.setState({projectItems}); + try { + const res = await fetch('http://localhost:8000/api/projects/'); + const projectItems = await res.json(); + this.setState({projectItems}); + } catch (e) { + console.log(e); + } + } + + render() { + return ( +
+
+
+
+
    + {this.state.projectItems.map(item => ( +
    +

    {item.title}

    + {item.description} +
    + ))} +
+
+
+
+
+ ) + } +} + +export default Projects; \ No newline at end of file diff --git a/herreweb_frontend/src/index.js b/herreweb_frontend/src/index.js index d563c0f..79a2139 100644 --- a/herreweb_frontend/src/index.js +++ b/herreweb_frontend/src/index.js @@ -1,13 +1,15 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; +import './static/index.css'; +import App from './components/App'; + import reportWebVitals from './reportWebVitals'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( + {/* */} ); diff --git a/herreweb_frontend/src/App.css b/herreweb_frontend/src/static/App.css similarity index 100% rename from herreweb_frontend/src/App.css rename to herreweb_frontend/src/static/App.css diff --git a/herreweb_frontend/src/static/AppHeader.css b/herreweb_frontend/src/static/AppHeader.css new file mode 100644 index 0000000..a3986c4 --- /dev/null +++ b/herreweb_frontend/src/static/AppHeader.css @@ -0,0 +1,109 @@ +.Header { + position: fixed; + top: 0; /* Stick it to the top */ + max-height: 70px; + width: 100vw; + + display: grid; + grid-template-areas: "logo nav"; + + /* Cosmetics */ + background-color: #282c34; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); + } + + .Logo { + grid-area: logo; + height: 70px; + } + + .Nav { + display: grid; + grid-area: nav; + grid-template-columns: repeat(4, auto); + align-items: center; + justify-items: center; + } + .Nav a { + color: #fff; + font-size: 20px; + font-weight: 500; + transition: 0.5s; + text-decoration: none; + } + .Nav a:hover { + transform: scale(1.1); + } + .Nav button { + padding: 10px; + outline: none; + border: none; + font-size: 20px; + color: #fff; + font-weight: 600; + background-color: rgba(255, 0, 0, 0.5); + box-shadow: 0px 5px 0px 0px rgba(255, 0, 0, 0.25); + border-radius: 10px; + cursor: pointer; + transition: 70ms; + } + + .Nav button:active { + transform: translateY(3px); + box-shadow: 0px 2px 0px 0px rgba(255, 0, 0, 0.25); + } + + .Burger { + display: none; + grid-area: burger; + margin: 0 20px 0 0; + padding: 0; + justify-self: end; + font-size: 40px; + border: none; + background: none; + outline: none; + transition: 0.1s; + } + .Burger:active { + transform: scale(1.2); + } + + @media (max-width: 700px) { + .Header { + grid-template-areas: "logo burger" "nav nav"; + } + .Nav { + grid-template-rows: repeat(4, auto); + grid-template-columns: none; + grid-row-gap: 20px; + + padding: 30px 0 30px; + background: rgba(40, 44, 47, 0.95); + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + } + .Burger { + display: inline; + } + } + + .NavAnimation-enter { + opacity: 0; + transform: scale(0.5); + } + .NavAnimation-enter-active { + opacity: 1; + transform: translateX(0); + transition: opacity 350ms, transform 350ms; + } + .NavAnimation-exit { + opacity: 1; + } + .NavAnimation-exit-active { + opacity: 0; + transform: scale(0.5); + transition: opacity 350ms, transform 350ms; + } + \ No newline at end of file diff --git a/herreweb_frontend/src/static/Projects.css b/herreweb_frontend/src/static/Projects.css new file mode 100644 index 0000000..e69de29 diff --git a/herreweb_frontend/src/index.css b/herreweb_frontend/src/static/index.css similarity index 100% rename from herreweb_frontend/src/index.css rename to herreweb_frontend/src/static/index.css diff --git a/herreweb_frontend/src/logo.svg b/herreweb_frontend/src/static/logo.svg similarity index 100% rename from herreweb_frontend/src/logo.svg rename to herreweb_frontend/src/static/logo.svg diff --git a/requirements.txt b/requirements.txt index 088653e..3b14feb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ django sphinx djangorestframework -django-cors-headers \ No newline at end of file +django-cors-headers +Pillow