Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • aasmuha/tdt4242-base
  • sebastvi/tdt4242-base
  • haavafar/tdt4242-base
  • tdt4242-spring-2021-t17/tdt4242-base
  • tmwang/tdt4242-base
  • tdt4242-group-t4/tdt4242-base
  • andstorh/tdt4242-base
  • reaas/tdt4242-base
  • andrerim/tdt4242-base
  • kristohh/tdt4242-base
  • andreajj/tdt4242-base
  • vegarms/tdt4242-base
  • andrend/tdt4242-base
  • haavarhu/tdt4242-base
  • mathilah/tdt4242-base
  • tmmothe/tdt4242-base
  • miriams/tdt4242-base
  • harkamas/tdt4242-base
  • chrisclo/tdt-4242-group-13
  • estherv/tdt4242-base
  • mariueng/tdt-4242-secfit
  • haakogun/secfit-haakon
  • haakonrj/tdt-4242-35
  • tdt4242-group-5/secfit
  • haakonjf/tdt-4242-gruppe6
  • nikolard/tdt-4242-sec-fit
  • tdt4242-gr24/tdt4242-base
  • abdulfaa/tdt4242-base
  • jmjohnse/tdt-4242-group-37-use
  • owengg/tdt-4242-base-owen-forked
  • ktsiow/tdt4242-base
  • olechrib/tdt4242-group16
  • eytan/tdt-4242-base-group-33
33 results
Show changes
Commits on Source (27)
Showing
with 1125 additions and 139 deletions
stages:
- test
- staging
test:
image: python:3.8
stage: test
script:
# this configures Django application to use attached postgres database that is run on `postgres` host
- cd backend/secfit
- apt-get update -qy
- pip install -r requirements.txt
- python manage.py test
staging:
type: deploy
image: ruby
stage: staging
script:
- apt-get update -qy
- apt-get install -y ruby-dev
- gem install dpl
- dpl --provider=heroku --app=secfit-1-frontend --api-key=$HEROKU_STAGING_API_KEY
- dpl --provider=heroku --app=secfit-1-backend --api-key=$HEROKU_STAGING_API_KEY
only:
- master
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (venv)" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tdt4242-base.iml" filepath="$PROJECT_DIR$/.idea/tdt4242-base.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
This diff is collapsed.
web: gunicorn --pythonpath 'backend/secfit' secfit.wsgi --log-file -
\ No newline at end of file
#import logging
import os
import dj_database_url
from django.test.runner import DiscoverRunner
MAX_CONN_AGE = 600
def settings(config, *, db_colors=False, databases=True, test_runner=True, staticfiles=True, allowed_hosts=True, logging=True, secret_key=True):
# Database configuration.
# TODO: support other database (e.g. TEAL, AMBER, etc, automatically.)
if databases:
# Integrity check.
if 'DATABASES' not in config:
config['DATABASES'] = {'default': None}
conn_max_age = config.get('CONN_MAX_AGE', MAX_CONN_AGE)
if db_colors:
# Support all Heroku databases.
# TODO: This appears to break TestRunner.
for (env, url) in os.environ.items():
if env.startswith('HEROKU_POSTGRESQL'):
db_color = env[len('HEROKU_POSTGRESQL_'):].split('_')[0]
#logger.info('Adding ${} to DATABASES Django setting ({}).'.format(env, db_color))
config['DATABASES'][db_color] = dj_database_url.parse(url, conn_max_age=conn_max_age, ssl_require=True)
if 'DATABASE_URL' in os.environ:
#logger.info('Adding $DATABASE_URL to default DATABASE Django setting.')
# Configure Django for DATABASE_URL environment variable.
config['DATABASES']['default'] = dj_database_url.config(conn_max_age=conn_max_age, ssl_require=True)
#logger.info('Adding $DATABASE_URL to TEST default DATABASE Django setting.')
# Enable test database if found in CI environment.
if 'CI' in os.environ:
config['DATABASES']['default']['TEST'] = config['DATABASES']['default']
#else:
#logger.info('$DATABASE_URL not found, falling back to previous settings!')
if test_runner:
# Enable test runner if found in CI environment.
if 'CI' in os.environ:
config['TEST_RUNNER'] = 'django_heroku.HerokuDiscoverRunner'
# Staticfiles configuration.
if staticfiles:
#logger.info('Applying Heroku Staticfiles configuration to Django settings.')
config['STATIC_ROOT'] = os.path.join(config['BASE_DIR'], 'staticfiles')
config['STATIC_URL'] = '/static/'
# Ensure STATIC_ROOT exists.
os.makedirs(config['STATIC_ROOT'], exist_ok=True)
# Insert Whitenoise Middleware.
try:
config['MIDDLEWARE_CLASSES'] = tuple(['whitenoise.middleware.WhiteNoiseMiddleware'] + list(config['MIDDLEWARE_CLASSES']))
except KeyError:
config['MIDDLEWARE'] = tuple(['whitenoise.middleware.WhiteNoiseMiddleware'] + list(config['MIDDLEWARE']))
# Enable GZip.
config['STATICFILES_STORAGE'] = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
if allowed_hosts:
#logger.info('Applying Heroku ALLOWED_HOSTS configuration to Django settings.')
config['ALLOWED_HOSTS'] = ['*']
"""
if logging:
logger.info('Applying Heroku logging configuration to Django settings.')
config['LOGGING'] = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': ('%(asctime)s [%(process)d] [%(levelname)s] ' +
'pathname=%(pathname)s lineno=%(lineno)s ' +
'funcname=%(funcName)s %(message)s'),
'datefmt': '%Y-%m-%d %H:%M:%S'
},
'simple': {
'format': '%(levelname)s %(message)s'
}
},
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'logging.NullHandler',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose'
}
},
'loggers': {
'testlogger': {
'handlers': ['console'],
'level': 'INFO',
}
}
}
"""
# SECRET_KEY configuration.
if secret_key:
if 'SECRET_KEY' in os.environ:
#logger.info('Adding $SECRET_KEY to SECRET_KEY Django setting.')
# Set the Django setting from the environment variable.
config['SECRET_KEY'] = os.environ['SECRET_KEY']
......@@ -12,7 +12,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
from pathlib import Path
import os
from .djangoHeroku import settings
# Get the GROUPID variable to accept connections from the application server and NGINX
groupid = os.environ.get("GROUPID", "0")
......@@ -43,6 +43,7 @@ ALLOWED_HOSTS = [
"10." + groupid + ".0.4",
"molde.idi.ntnu.no",
"10.0.2.2",
"secfit-1-backend.herokuapp.com"
]
# Application definition
......@@ -95,17 +96,27 @@ WSGI_APPLICATION = "secfit.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
is_prod = os.environ.get("IS_HEROKU", None)
if is_prod:
settings(locals())
if 'DATABASE_URL' in os.environ:
import dj_database_url
print("\n\n\n\n\nHEI\n\n\n\n\n\n")
DATABASES = {'default': dj_database_url.config()}
else:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
}
# CORS Policy
CORS_ORIGIN_ALLOW_ALL = (
True
)
CORS_ORIGIN_ALLOW_ALL = True
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
......@@ -136,8 +147,12 @@ MEDIA_URL = "/media/"
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
#"DEFAULT_AUTHENTICATION_CLASSES": (
# "rest_framework_simplejwt.authentication.JWTAuthentication",
#),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
"rest_framework_simplejwt.authentication.JWTAuthentication"
),
}
......
......@@ -3,9 +3,10 @@
from django.contrib import admin
# Register your models here.
from .models import Exercise, ExerciseInstance, Workout, WorkoutFile
from .models import Exercise, ExerciseInstance, Workout, WorkoutFile, WorkoutLike
admin.site.register(Exercise)
admin.site.register(ExerciseInstance)
admin.site.register(Workout)
admin.site.register(WorkoutFile)
admin.site.register(WorkoutLike)
# Generated by Django 3.1 on 2021-02-21 14:35
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('workouts', '0003_rememberme'),
]
operations = [
migrations.CreateModel(
name='WorkoutLike',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('userLiking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='userLiking', to=settings.AUTH_USER_MODEL)),
('workoutToLike', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workouts.workout')),
],
),
]
......@@ -151,3 +151,15 @@ class RememberMe(models.Model):
def __str__(self):
return self.remember_me
# Likes for a workout
class WorkoutLike(models.Model):
# The workout that is being liked
workoutToLike = models.ForeignKey(Workout, on_delete=models.CASCADE)
# The user doing the liking
userLiking = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE, related_name="userLiking"
)
\ No newline at end of file
......@@ -4,3 +4,13 @@ Tests for the workouts application.
from django.test import TestCase
# Create your tests here.
class TestTestCase(TestCase):
def test_true(self):
"""Check if true is true"""
self.assertTrue(True)
def test_false(self):
"""Check if false is false"""
self.assertFalse(False)
\ No newline at end of file
......@@ -27,6 +27,16 @@ urlpatterns = format_suffix_patterns(
views.ExerciseInstanceList.as_view(),
name="exercise-instance-list",
),
path(
"api/leaderboards/<int:pk>/",
views.Leaderboards.as_view(),
name="leaderboards",
),
path(
"api/workoutLiking/<int:pk>/",
views.WorkoutLiking.as_view(),
name="WorkoutLiking",
),
path(
"api/exercise-instances/<int:pk>/",
views.ExerciseInstanceDetail.as_view(),
......
......@@ -6,10 +6,12 @@ from rest_framework import permissions
from rest_framework.parsers import (
JSONParser,
)
import json
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from django.db.models import Q
from django.db.models import Q, Sum, F, IntegerField
from rest_framework import filters
from workouts.parsers import MultipartJsonParser
from workouts.permissions import (
......@@ -22,7 +24,7 @@ from workouts.permissions import (
IsWorkoutPublic,
)
from workouts.mixins import CreateListModelMixin
from workouts.models import Workout, Exercise, ExerciseInstance, WorkoutFile
from workouts.models import Workout, Exercise, ExerciseInstance, WorkoutFile, WorkoutLike
from workouts.serializers import WorkoutSerializer, ExerciseSerializer
from workouts.serializers import RememberMeSerializer
from workouts.serializers import ExerciseInstanceSerializer, WorkoutFileSerializer
......@@ -34,6 +36,9 @@ from collections import namedtuple
import base64, pickle
from django.core.signing import Signer
from users.models import User
from rest_framework.views import APIView
@api_view(["GET"])
def api_root(request, format=None):
......@@ -204,7 +209,6 @@ class ExerciseDetail(
HTTP methods: GET, PUT, PATCH, DELETE
"""
queryset = Exercise.objects.all()
serializer_class = ExerciseSerializer
permission_classes = [permissions.IsAuthenticated]
......@@ -221,6 +225,43 @@ class ExerciseDetail(
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class Leaderboards(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request, pk):
# User must be logged in
if self.request.user:
leaderboardNumbers = ExerciseInstance.objects.filter(Q(exercise__pk=pk) & Q(workout__visibility='PU')).values('workout__owner__pk').annotate(amount=Sum(F("sets") * F("number"), output_field=IntegerField())).order_by('-amount')
leaderboardResult = []
# Iterates through the top 5 entries in the leaderboard and formats it correctly
for i in range(0, min(5, len(leaderboardNumbers))):
leaderboardResult.append({"name": User.objects.get(pk=leaderboardNumbers[i]['workout__owner__pk']).username, "value": leaderboardNumbers[i]['amount']})
# Applies the rank to the leaderboard entry; if two or more users have the score they get the same rank
if i > 0 and leaderboardNumbers[i-1]["amount"] == leaderboardNumbers[i]["amount"]:
leaderboardResult[i]["rank"] = leaderboardResult[i-1]["rank"]
else:
leaderboardResult[i]["rank"] = i+1
# Finds the user in the leaderboard list. If the user is not in the leaderboard list,
# the user is automatically given a score of 0 and the worst rank
currentLoggedInUser = self.request.user
for j in range(0, len(leaderboardNumbers)):
if leaderboardNumbers[j]['workout__owner__pk'] == currentLoggedInUser.pk:
leaderboardResult.append({"name": currentLoggedInUser.username, "value": leaderboardNumbers[j]["amount"], "rank": j+1})
break
else:
leaderboardResult.append({"name": currentLoggedInUser.username, "value": 0, "rank": len(leaderboardNumbers) + 1})
return Response(leaderboardResult)
class ExerciseInstanceList(
mixins.ListModelMixin,
......@@ -340,3 +381,35 @@ class WorkoutFileDetail(
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
# View for fetching like amount, and for creating new likes
class WorkoutLiking(APIView):
permission_classes = [permissions.IsAuthenticated]
# Returns a tuple with a boolean value that is true if liking is allowed (the workout does not belong to the user
# and the workout has not been liked before), and the amount of likes that the workout has
def get(self, request, pk):
likingAllowed = Workout.objects.get(pk=pk).owner != self.request.user and WorkoutLike.objects.filter(
Q(userLiking=self.request.user) & Q(workoutToLike__pk=pk)).count() == 0
likeAmount = WorkoutLike.objects.filter(Q(workoutToLike__pk=pk)).count() + 1
return Response((likingAllowed, likeAmount), status.HTTP_200_OK)
# Tries to like a new post and returns the same as the GET above
def post(self, request, pk):
likingAllowed = Workout.objects.get(pk=pk).owner != self.request.user and WorkoutLike.objects.filter(
Q(userLiking=self.request.user) & Q(workoutToLike__pk=pk)).count() == 0
likeAmount = WorkoutLike.objects.filter(Q(workoutToLike__pk=pk)).count() + 1
if likingAllowed:
newWorkoutLike = WorkoutLike(workoutToLike=Workout.objects.get(pk=pk), userLiking=self.request.user)
newWorkoutLike.save()
return Response((False, likeAmount + 1), status.HTTP_201_CREATED)
return Response((likingAllowed, likeAmount), status.HTTP_100_CONTINUE)
\ No newline at end of file
web: cd frontend && cordova run browser --release --port=$PORT
\ No newline at end of file
......@@ -45,7 +45,15 @@
</div>
</form>
<div class="row">
<div class="col-lg">
<h3 class="mt-3">Leaderboard</h3>
</div>
</div>
<table id="leaderboardstable" class="table table-striped">
<tr><th>Rank<th>Username<th>Score
</table>
</div>
<script src="scripts/defaults.js"></script>
<script src="scripts/scripts.js"></script>
<script src="scripts/exercise.js"></script>
......
......@@ -4,151 +4,200 @@ let deleteButton;
let editButton;
let oldFormData;
function handleCancelButtonDuringEdit() {
setReadOnly(true, "#form-exercise");
okButton.className += " hide";
deleteButton.className += " hide";
cancelButton.className += " hide";
editButton.className = editButton.className.replace(" hide", "");
cancelButton.removeEventListener("click", handleCancelButtonDuringEdit);
let form = document.querySelector("#form-exercise");
if (oldFormData.has("name")) form.name.value = oldFormData.get("name");
if (oldFormData.has("description")) form.description.value = oldFormData.get("description");
if (oldFormData.has("unit")) form.unit.value = oldFormData.get("unit");
oldFormData.delete("name");
oldFormData.delete("description");
oldFormData.delete("unit");
setReadOnly(true, "#form-exercise");
okButton.className += " hide";
deleteButton.className += " hide";
cancelButton.className += " hide";
editButton.className = editButton.className.replace(" hide", "");
cancelButton.removeEventListener("click", handleCancelButtonDuringEdit);
let form = document.querySelector("#form-exercise");
if (oldFormData.has("name")) form.name.value = oldFormData.get("name");
if (oldFormData.has("description"))
form.description.value = oldFormData.get("description");
if (oldFormData.has("unit")) form.unit.value = oldFormData.get("unit");
oldFormData.delete("name");
oldFormData.delete("description");
oldFormData.delete("unit");
}
function handleCancelButtonDuringCreate() {
window.location.replace("exercises.html");
window.location.replace("exercises.html");
}
async function createExercise() {
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
let body = {"name": formData.get("name"),
"description": formData.get("description"),
"unit": formData.get("unit")};
let response = await sendRequest("POST", `${HOST}/api/exercises/`, body);
if (response.ok) {
window.location.replace("exercises.html");
} else {
let data = await response.json();
let alert = createAlert("Could not create new exercise!", data);
document.body.prepend(alert);
}
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
let body = {
name: formData.get("name"),
description: formData.get("description"),
unit: formData.get("unit"),
};
let response = await sendRequest("POST", `${HOST}/api/exercises/`, body);
if (response.ok) {
window.location.replace("exercises.html");
} else {
let data = await response.json();
let alert = createAlert("Could not create new exercise!", data);
document.body.prepend(alert);
}
}
function handleEditExerciseButtonClick() {
setReadOnly(false, "#form-exercise");
setReadOnly(false, "#form-exercise");
editButton.className += " hide";
okButton.className = okButton.className.replace(" hide", "");
cancelButton.className = cancelButton.className.replace(" hide", "");
deleteButton.className = deleteButton.className.replace(" hide", "");
editButton.className += " hide";
okButton.className = okButton.className.replace(" hide", "");
cancelButton.className = cancelButton.className.replace(" hide", "");
deleteButton.className = deleteButton.className.replace(" hide", "");
cancelButton.addEventListener("click", handleCancelButtonDuringEdit);
cancelButton.addEventListener("click", handleCancelButtonDuringEdit);
let form = document.querySelector("#form-exercise");
oldFormData = new FormData(form);
let form = document.querySelector("#form-exercise");
oldFormData = new FormData(form);
}
async function deleteExercise(id) {
let response = await sendRequest("DELETE", `${HOST}/api/exercises/${id}/`);
if (!response.ok) {
let data = await response.json();
let alert = createAlert(`Could not delete exercise ${id}`, data);
document.body.prepend(alert);
} else {
window.location.replace("exercises.html");
}
let response = await sendRequest("DELETE", `${HOST}/api/exercises/${id}/`);
if (!response.ok) {
let data = await response.json();
let alert = createAlert(`Could not delete exercise ${id}`, data);
document.body.prepend(alert);
} else {
window.location.replace("exercises.html");
}
}
async function retrieveExercise(id) {
let response = await sendRequest("GET", `${HOST}/api/exercises/${id}/`);
console.log(response.ok);
if (!response.ok) {
let data = await response.json();
let alert = createAlert("Could not retrieve exercise data!", data);
document.body.prepend(alert);
} else {
let exerciseData = await response.json();
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
for (let key of formData.keys()) {
let selector = `input[name="${key}"], textarea[name="${key}"]`;
let input = form.querySelector(selector);
let newVal = exerciseData[key];
input.value = newVal;
}
let response = await sendRequest("GET", `${HOST}/api/exercises/${id}/`);
console.log(response.ok);
if (!response.ok) {
let data = await response.json();
let alert = createAlert("Could not retrieve exercise data!", data);
document.body.prepend(alert);
} else {
let exerciseData = await response.json();
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
for (let key of formData.keys()) {
let selector = `input[name="${key}"], textarea[name="${key}"]`;
let input = form.querySelector(selector);
let newVal = exerciseData[key];
input.value = newVal;
}
}
}
async function updateExercise(id) {
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
let body = {"name": formData.get("name"),
"description": formData.get("description"),
"unit": formData.get("unit")};
let response = await sendRequest("PUT", `${HOST}/api/exercises/${id}/`, body);
if (!response.ok) {
let data = await response.json();
let alert = createAlert(`Could not update exercise ${id}`, data);
document.body.prepend(alert);
} else {
// duplicate code from handleCancelButtonDuringEdit
// you should refactor this
setReadOnly(true, "#form-exercise");
okButton.className += " hide";
deleteButton.className += " hide";
cancelButton.className += " hide";
editButton.className = editButton.className.replace(" hide", "");
cancelButton.removeEventListener("click", handleCancelButtonDuringEdit);
oldFormData.delete("name");
oldFormData.delete("description");
oldFormData.delete("unit");
let form = document.querySelector("#form-exercise");
let formData = new FormData(form);
let body = {
name: formData.get("name"),
description: formData.get("description"),
unit: formData.get("unit"),
};
let response = await sendRequest("PUT", `${HOST}/api/exercises/${id}/`, body);
if (!response.ok) {
let data = await response.json();
let alert = createAlert(`Could not update exercise ${id}`, data);
document.body.prepend(alert);
} else {
// duplicate code from handleCancelButtonDuringEdit
// you should refactor this
setReadOnly(true, "#form-exercise");
okButton.className += " hide";
deleteButton.className += " hide";
cancelButton.className += " hide";
editButton.className = editButton.className.replace(" hide", "");
cancelButton.removeEventListener("click", handleCancelButtonDuringEdit);
oldFormData.delete("name");
oldFormData.delete("description");
oldFormData.delete("unit");
}
}
async function fetchLeaderBoards(id) {
// Fetches leaderboard data
let response = await sendRequest("GET", `${HOST}/api/leaderboards/${id}/`);
let data = await response.json();
if (response.ok) {
let table = document.getElementById("leaderboardstable");
let row, cell;
//The user's own score will always be placed last in the JSON response
let userIndex = data.length - 1;
for (let i = 0; i < data.length - 1; i++) {
row = table.insertRow();
cell = row.insertCell();
cell.textContent = data[i].rank;
cell = row.insertCell();
cell.textContent = data[i].name;
cell = row.insertCell();
cell.textContent = data[i].value;
}
//If the user is not in top 5, the users score will also be rendered
if (data[userIndex].rank > 5) {
row = table.insertRow();
cell = row.insertCell();
cell.textContent = data[userIndex].rank;
cell = row.insertCell();
cell.textContent = data[userIndex].name;
cell = row.insertCell();
cell.textContent = data[userIndex].value;
}
}
return data;
}
window.addEventListener("DOMContentLoaded", async () => {
cancelButton = document.querySelector("#btn-cancel-exercise");
okButton = document.querySelector("#btn-ok-exercise");
deleteButton = document.querySelector("#btn-delete-exercise");
editButton = document.querySelector("#btn-edit-exercise");
oldFormData = null;
const urlParams = new URLSearchParams(window.location.search);
// view/edit
if (urlParams.has('id')) {
const exerciseId = urlParams.get('id');
await retrieveExercise(exerciseId);
editButton.addEventListener("click", handleEditExerciseButtonClick);
deleteButton.addEventListener("click", (async (id) => await deleteExercise(id)).bind(undefined, exerciseId));
okButton.addEventListener("click", (async (id) => await updateExercise(id)).bind(undefined, exerciseId));
}
//create
else {
setReadOnly(false, "#form-exercise");
editButton.className += " hide";
okButton.className = okButton.className.replace(" hide", "");
cancelButton.className = cancelButton.className.replace(" hide", "");
okButton.addEventListener("click", async () => await createExercise());
cancelButton.addEventListener("click", handleCancelButtonDuringCreate);
}
});
\ No newline at end of file
cancelButton = document.querySelector("#btn-cancel-exercise");
okButton = document.querySelector("#btn-ok-exercise");
deleteButton = document.querySelector("#btn-delete-exercise");
editButton = document.querySelector("#btn-edit-exercise");
oldFormData = null;
const urlParams = new URLSearchParams(window.location.search);
// view/edit
if (urlParams.has("id")) {
const exerciseId = urlParams.get("id");
await retrieveExercise(exerciseId);
await fetchLeaderBoards(exerciseId);
editButton.addEventListener("click", handleEditExerciseButtonClick);
deleteButton.addEventListener(
"click",
(async (id) => await deleteExercise(id)).bind(undefined, exerciseId)
);
okButton.addEventListener(
"click",
(async (id) => await updateExercise(id)).bind(undefined, exerciseId)
);
}
//create
else {
setReadOnly(false, "#form-exercise");
editButton.className += " hide";
okButton.className = okButton.className.replace(" hide", "");
cancelButton.className = cancelButton.className.replace(" hide", "");
okButton.addEventListener("click", async () => await createExercise());
cancelButton.addEventListener("click", handleCancelButtonDuringCreate);
}
});
......@@ -8,7 +8,17 @@ async function fetchWorkouts(ordering) {
let workouts = data.results;
let container = document.getElementById('div-content');
workouts.forEach(workout => {
for(const workout of workouts){
let workoutLikes = await sendRequest("GET", `${HOST}/api/workoutLiking/${workout.id}`);
let workoutLikesData = [true, 1]
if(workoutLikes.ok){
workoutLikesData = await workoutLikes.json()
}
let templateWorkout = document.querySelector("#template-workout");
let cloneWorkout = templateWorkout.content.cloneNode(true);
......@@ -27,8 +37,35 @@ async function fetchWorkouts(ordering) {
rows[2].querySelectorAll("td")[1].textContent = workout.owner_username; //Owner
rows[3].querySelectorAll("td")[1].textContent = workout.exercise_instances.length; // Exercises
rows[4].querySelectorAll("td")[1].textContent = workoutLikesData[1]
let likeButton = rows[4].querySelectorAll("td")[2].querySelector(".like-button")
if(!workoutLikesData[0]){
likeButton.classList.add("active")
}
likeButton.addEventListener("click", async function(e) {
e.preventDefault();
if(!this.classList.contains("active")){
this.classList.add("active");
this.classList.add("animated");
generateClones(this);
let likeWorkoutResponse = await sendRequest("POST", `${HOST}/api/workoutLiking/${workout.id}/`, {});
if(likeWorkoutResponse.ok){
let likeWorkoutData = await likeWorkoutResponse.json()
rows[4].querySelectorAll("td")[1].textContent = likeWorkoutData[1]
likeButton.classList.add("active")
}
}
})
container.appendChild(aWorkout);
});
};
return workouts;
}
}
......@@ -103,4 +140,48 @@ window.addEventListener("DOMContentLoaded", async () => {
}
});
}
});
\ No newline at end of file
});
function generateClones(button) {
let clones = randomInt(4, 7);
for (let it = 1; it <= clones; it++) {
let clone = button.querySelector("svg").cloneNode(true),
size = randomInt(5, 16);
button.appendChild(clone);
clone.setAttribute("width", size);
clone.setAttribute("height", size);
clone.style.position = "absolute";
clone.style.transition =
"transform 0.5s cubic-bezier(0.12, 0.74, 0.58, 0.99) 0.3s, opacity 1s ease-out .5s";
let animTimeout = setTimeout(function() {
clearTimeout(animTimeout);
clone.style.transform =
"translate3d(" +
(plusOrMinus() * randomInt(10, 25)) +
"px," +
(plusOrMinus() * randomInt(10, 25)) +
"px,0)";
clone.style.opacity = 0;
}, 1);
let removeNodeTimeout = setTimeout(function() {
clone.parentNode.removeChild(clone);
clearTimeout(removeNodeTimeout);
}, 900);
let removeClassTimeout = setTimeout( function() {
button.classList.remove("animated")
}, 600);
}
}
function plusOrMinus() {
return Math.random() < 0.5 ? -1 : 1;
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}