diff --git a/.gitignore b/.gitignore index 065a47d3..db1a933b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,6 @@ efs/* # ignore sent email dir for tests app-messages/* +CommunicationSeminar-7d* # local dev settings module local_dev.py diff --git a/ComSemApp/administrator/forms.py b/ComSemApp/administrator/forms.py index 720ce6c2..2212d0be 100644 --- a/ComSemApp/administrator/forms.py +++ b/ComSemApp/administrator/forms.py @@ -6,7 +6,7 @@ from django_select2.forms import Select2MultipleWidget from django.contrib.auth.models import User -from ComSemApp.models import Course, CourseType, Session, SessionType, Teacher, Student, Institution +from ComSemApp.models import * @@ -86,3 +86,16 @@ class StudentForm(ModelForm): class Meta: model = Student fields = ['country', 'language'] + +class ReplyForm(ModelForm): + message = forms.CharField( widget=forms.Textarea(attrs={'style': "width:100%", 'placeholder': 'Enter reply here.'})) + class Meta: + model = Reply + fields = ["message"] + +class TopicForm(ModelForm): + message = forms.CharField( widget=forms.Textarea(attrs={'style': "width:100%"})) + title = forms.CharField(widget=forms.TextInput(attrs={'style': "width:100%"})) + class Meta: + model = Reply + fields = ["title", "message"] \ No newline at end of file diff --git a/ComSemApp/administrator/urls.py b/ComSemApp/administrator/urls.py index 7e1730d4..38b357cd 100644 --- a/ComSemApp/administrator/urls.py +++ b/ComSemApp/administrator/urls.py @@ -2,6 +2,8 @@ from ComSemApp.administrator import views from ComSemApp.corpus import views as corpus_views +from ComSemApp.discussionBoard import view as discussion_views + app_name = 'admin' urlpatterns = [ url(r'^$', views.TeacherListView.as_view(), name='home'), @@ -37,5 +39,8 @@ url(r'^session_type/(?P[0-9]+)/delete/$', views.SessionTypeDeleteView.as_view(), name='delete_session_type'), url(r'^corpus/search$', corpus_views.corpus_search, name='corpus_search'), + url(r'^discussion_board$', discussion_views.TopicListView.as_view(), name='admin_discussion_board'), + url(r'^topic/(?P[0-9]+)/$', discussion_views.ReplyView.as_view(), name='admin_topic'), + url(r'^newtopic/$', discussion_views.CreateThreadView.as_view(),name='admin_create_topic') ] diff --git a/ComSemApp/administrator/views.py b/ComSemApp/administrator/views.py index 57de0666..0600a5a0 100644 --- a/ComSemApp/administrator/views.py +++ b/ComSemApp/administrator/views.py @@ -13,13 +13,20 @@ from django.views import View from django.core.mail import send_mail +from django.core.validators import validate_email from django.contrib import messages +import csv +import io +import re + from ComSemApp.models import * from django.contrib.auth.models import User from ComSemApp.administrator.forms import CourseForm, CourseTypeForm, SessionForm, SessionTypeForm, TeacherForm, StudentForm, UserForm from ComSemApp.libs.mixins import RoleViewMixin +from django.core.exceptions import ValidationError + class AdminViewMixin(RoleViewMixin): @@ -53,10 +60,135 @@ def get_queryset(self): class StudentListView(AdminViewMixin, ListView): model = Student template_name = 'ComSemApp/admin/student_list.html' + success_url = reverse_lazy("administrator:students") + + def _send_email(self, user, password): + link = "https://www.comsem.net" + message = ("You have been invited to join Communication Seminar by an administrator for " + self.institution.name + ".\n" + "In order to log in, go to " + link + " and use \n" + "\tusername: " + user.username + "\n\tpassword: " + password + "\n" + "from there you can change your password.") + + send_mail( + 'Invitation to Communication Seminar', + message, + 'signup@comsem.net', + [user.email], + fail_silently=False, + ) + + + def db_create_user(self, **kwargs): + user = User.objects.create(**kwargs) + password = User.objects.make_random_password() + user.set_password(password) + user.save() + self._send_email(user, password) + return user + + def db_create_student(self, **kwargs): + institution = self.institution + user = self.db_create_user(**kwargs) + return Student.objects.create(user=user, institution=institution) + + #handle CSV upload + def post(self, request, *args, **kwargs): + if (len(request.FILES) > 0): #check to make sure file was uploaded + csv_file = request.FILES['file'] + file_data = csv_file.read().decode("utf-8") + lines = file_data.split("\n") + rejectedLines = [] + message_content = [""] + linecount = 0 + rejectcount = 0 + for line in lines: + if len(line): #make sure line isnt empty + fields = line.split(",") + okToCreate = True + rejected = False + linecount += 1 + first = fields[0].rstrip() + last = fields[1].rstrip() + email = fields[2].rstrip() + username = fields[3].rstrip() + if (first == "" or first == ""): + #end of file + break + if (len(fields) < 4): + message = "!!! Missing columns, please make sure you have columns as follows: firstname,lastname,email,username" + message_content.append(message) + rejected = True + rejectcount += 1 + break + if (first.isalpha() == False or last.isalpha() == False): + message = (str(linecount) + " " + first + " " + last + " " + email + " " + username + " Invalid First or Last Name \n") + message_content.append(message) + rejectcount += 1 + rejected = True + okToCreate = False + for user in Student.objects.filter(institution=self.institution): + if(user.user.email== email): + okToCreate = False + if (rejected == False): ##if rejected is false, we need to increment the number of rejects, if its already false, dont increment it but still log error + rejectcount += 1 + rejected = True + message = (str(linecount) + " " + first + " " + last + " " + email + " " + username + " Duplicate Email Address \n") + message_content.append(message) + + if(user.user.username== username): + okToCreate = False + if (rejected == False): ##if rejected is false, we need to increment the number of rejects, if its already false, dont increment it but still log error + rejectcount += 1 + rejected = True + message = (str(linecount) + " " + first + " " + last + " " + email + " " + username + " Duplicate Username \n") + message_content.append(message) + if(okToCreate == False): + break + + # Check if a valid email address + match = re.match('^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email.lower()) + + if (match == None): + if(rejected == False): + rejectcount += 1 + rejected = True + okToCreate = False + message = (str(linecount) + " " + first + " " + last + " " + email + " " + username + " Invalid Email Address \n") + message_content.append(message) + + # Check for valid username + usernameCheck = re.match('^[\w.@+-]+$', username.rstrip()) + print(usernameCheck) + if (usernameCheck == None): + if(rejected == False): + rejectcount += 1 + rejected = True + okToCreate = False + message = (str(linecount) + " " + first + " " + last + " " + email + " " + username + " Invalid Username \n") + message_content.append(message) + + if (okToCreate == True): + user = { + "first_name": first, + "last_name": last, + "email": email, + "username": username, + } + self.db_create_student(**user) + print("student made") + message_content.insert(0, ("" + str((linecount - rejectcount)) + "/" + str(linecount)+ " Accounts created sucessfully\n" + "The below users were not added, Their line numbers are listed to the left,\nLines with multiple errors will be listed multiple times \n \n")) + message_disp = "".join(message_content) + messages.add_message(request, messages.ERROR, message_disp) + request.FILES.pop('file', None) #delete the csv file from memory + return HttpResponseRedirect(self.success_url) + def get_queryset(self): + return Student.objects.filter(institution=self.institution) + + class CourseListView(AdminViewMixin, ListView): model = Course @@ -106,6 +238,7 @@ def form_invalid(self, user_form, obj_form, **kwargs): return self.render_to_response(self.get_context_data(form=user_form, obj_form=obj_form)) def _send_email(self, user, password): + print("EMAIL SENT") link = "https://www.comsem.net" message = ("You have been invited to join Communication Seminar by an administrator for " + self.institution.name + ".\n" "In order to log in, go to " + link + " and use \n" @@ -133,7 +266,6 @@ def get(self, request, *args, **kwargs): def post(self, request, *args, **kwargs): user_form = UserForm(self.request.POST, prefix='user_form') obj_form = self.get_obj_form() - if user_form.is_valid() and obj_form.is_valid(): # create the user object with random password user = user_form.save() diff --git a/ComSemApp/corpus/views.py b/ComSemApp/corpus/views.py index dffaa248..aa7f6455 100644 --- a/ComSemApp/corpus/views.py +++ b/ComSemApp/corpus/views.py @@ -19,11 +19,11 @@ def corpus_search(request): tags = Tag.objects.all() template = loader.get_template('ComSemApp/corpus/corpus_search.html') - return HttpResponse(template.render({'tags': tags}, request)) + return HttpResponse(template.render({'tags': tags, 'offsetRange':[i for i in range(-8,8+1)]}, request)) @login_required def populate_word_tag(request): - val = request.POST.get('val', None) + val = request.POST.get('val', None).strip() search_type = request.POST.get('type', None) output = request.POST.get('output', None) @@ -65,8 +65,12 @@ def search_results(request): search_criteria = json.loads(search_criteria) - query = build_query(len(search_criteria) - 1, search_criteria, sequential_search) - print(query) + for item in search_criteria: + if item['type'] == 'word' and " " in item['val'].rstrip().lstrip(): + return HttpResponse('Invalid input: one word only per entry'); + + query = build_query(search_criteria, sequential_search) + with connection.cursor() as cursor: expression_ids = [] cursor.execute(query) @@ -76,20 +80,6 @@ def search_results(request): # grab the information we want about the expressions expressions = Expression.objects.filter(id__in=expression_ids) - # for each expression, retag in order to show where the matching word / tag is. - # TODO - # for expression in expressions: - # tokens = nltk.word_tokenize(expression.expression) - # tagged = nltk.pos_tag(tokens) - # print (tagged) - # for criterion in search_criteria: - # print (criterion) - # if criterion['type'] == 'tag': - # tag = criterion['val'] - # for word in tagged: - # if word[1] == tag: - # print ("match") - context = { 'expressions': expressions, 'sequential_search': sequential_search, @@ -98,47 +88,49 @@ def search_results(request): template = loader.get_template('ComSemApp/corpus/search_results.html') return HttpResponse(template.render(context, request)) - -# work backwards through the search criteria - we make n - 1 joins (where n = number of search criteria) with n tables that -# select expression ID and position (if sequential search). -def build_query(i, search_criteria, sequential_search): - current_criteria = search_criteria[i] - criteria_type = current_criteria['type'] - val = current_criteria['val'] - id_list = current_criteria['id_list'] - - # if val isnt valid, id_list isn't a list of int ... - - if i < 0: +# This query builder makes the following assumptions about the search criteria: +# there is one word, either a tag or a second word, and there may be an offset. +def build_query(search_criteria, sequential_search): + words = [] + tags = [] + offset = 0 + for item in search_criteria: + if item['type'] == 'word': + words.append(item) + elif item['type'] == 'tag': + tags.append(item) + elif item['type'] == 'offset' and sequential_search == True: + offset = item['val'] + + if len(words) == 0: return "" - else: - if(criteria_type == "offset"): - print ("to do") - - select_position = ", SW.Position" if sequential_search else "" - from_words = ", ComSemApp_word as W " if criteria_type == "tag" else "" - - query = "SELECT SW.expression_id" + select_position + " FROM ComSemApp_sequentialwords AS SW" + from_words - if i > 0: - query += ", (" + build_query(i - 1, search_criteria, sequential_search) + ") as Derived" + str(i) - - query += " WHERE " - - if criteria_type == "tag": - query += " SW.word_id = W.id AND W.tag_id in (" + ','.join([str(id) for id in id_list]) + ") " + query = "SELECT SW.expression_id" + if sequential_search: + query += ", SW.position" + query += " FROM ComSemApp_sequentialwords as SW" + + if len(words) > 1 or len(tags) > 0: + query += ", (SELECT SW2.expression_id" + if sequential_search: + query += ", SW2.position" + query += " from ComSemApp_sequentialwords as SW2" + if len(tags) > 0: + query += ", ComSemApp_word as W where W.tag_id in (" + ','.join([str(id) for id in tags[0]['id_list']]) + query += ") and SW2.word_id = W.id" else: - query += " SW.word_id in (" + ','.join([str(id) for id in id_list]) + ") " - - if i > 0: - if sequential_search: - next_position = 1 + query += " where SW2.word_id in (" + ','.join([str(id) for id in words[1]['id_list']]) + query += ")" + query += ") as derived2" + query += " where SW.word_id in (" + ','.join([str(id) for id in words[0]['id_list']]) + query += ")" - # if the next search criteria is an offset, we'll use it here then skip it in the next call. - if search_criteria[i-1]['type'] == 'offset': - next_position += search_criteria[i-1]['val'] + if len(words) > 1 or len(tags) > 0: + query += " and SW.expression_id = derived2.expression_id" - query += "AND SW.position = (Derived" + str(i) + ".position + " + str(next_position) + ") " + if offset > 0: + query += " and derived2.position <= (SW.position + " + str(offset) + ") and SW.position < derived2.position" + elif offset < 0: + query += " and SW.position <= (derived2.position + " + str(abs(offset)) + ") and derived2.position < SW.position" - query += "AND SW.expression_id = Derived" + str(i) + ".expression_id " - return query + return query diff --git a/ComSemApp/discussionBoard/test/__init__.py b/ComSemApp/discussionBoard/test/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/ComSemApp/discussionBoard/test/__init__.py @@ -0,0 +1 @@ + diff --git a/ComSemApp/discussionBoard/test/test_views.py b/ComSemApp/discussionBoard/test/test_views.py new file mode 100644 index 00000000..f458ea21 --- /dev/null +++ b/ComSemApp/discussionBoard/test/test_views.py @@ -0,0 +1,32 @@ +from django.urls import reverse +from django.core import mail + +from ComSemApp.libs.factories import BaseTestCase +from ComSemApp.models import * + +class TestCredentials(BaseTestCase): + + # only students and teachers should be able to access students views. + + discussion_board_url = reverse("discussion_board:topics") + loggin_url = reverse("login") + + def setUp(self): + super(TestCredentials, self).setUp() + self.password = "password123" + self.teacher = self.db_create_teacher(password=self.password) + self.student = self.db_create_student(password=self.password) + + def test_not_logged_in_fail(self): + response = self.client.get(self.discussion_board_url) + self.assertRedirects(response, '%s?next=%s' % (self.login_url, self.discussion_board_url)) + + def test_logged_in_teacher_success(self): + self.client.login(username=self.teacher.user.username, password=self.password) + response = self.client.get(self.discussion_board_url) + self.assertEqual(response.status_code, 200) + + def test_logged_in_student_success(self): + self.client.login(username=self.student.user.username, password=self.password) + response = self.client.get(self.discussion_board_url) + self.assertEqual(response.status_code, 200) diff --git a/ComSemApp/discussionBoard/urls.py b/ComSemApp/discussionBoard/urls.py new file mode 100644 index 00000000..0d267c8e --- /dev/null +++ b/ComSemApp/discussionBoard/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls import url +from ComSemApp.discussionBoard import view + +app_name = 'discussion_board' +urlpatterns = [ + url(r'^$', view.TopicListView.as_view(), name='topics'), + url(r'^topic/(?P[0-9]+)/$', view.ReplyView.as_view(), name='topic'), + url(r'^newtopic/$', view.CreateThreadView.as_view(),name='create_topic') +] \ No newline at end of file diff --git a/ComSemApp/discussionBoard/view.py b/ComSemApp/discussionBoard/view.py new file mode 100644 index 00000000..3652101f --- /dev/null +++ b/ComSemApp/discussionBoard/view.py @@ -0,0 +1,171 @@ +import json + +from django.shortcuts import render +from django.http import HttpResponse +from django.template import loader +from django.shortcuts import get_object_or_404 +from django.contrib.auth.decorators import login_required +from django.contrib.auth.decorators import user_passes_test +from django.db.models import Q +from django.views.generic import ListView, DetailView, CreateView, UpdateView, FormView +from django.http import JsonResponse, HttpResponseRedirect +from django.urls import reverse +from django.contrib import messages +from django.urls import reverse_lazy +from django import forms +from django.urls import resolve +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin + +from ComSemApp.teacher import constants as teacher_constants + +from ComSemApp.models import * +from ComSemApp.libs.mixins import RoleViewMixin, CourseViewMixin, WorksheetViewMixin, SubmissionViewMixin +from ComSemApp.administrator.forms import ReplyForm, TopicForm + + +#This class deals with listing out all the topics within database +#brings back the Topic model and displays it using Django Listview +#This page requires you to be logged in to use +class TopicListView(LoginRequiredMixin,ListView): + model = Topic + template_name = 'ComSemApp/discussionBoard/topic_list.html' + context_object_name = 'topics' + + def get_queryset(self): + return Topic.objects.filter().order_by("-id") + +class ReplyMixin(LoginRequiredMixin, ListView, object): + context_object_name = 'replies' + template_name = 'ComSemApp/discussionBoard/add_reply.html' + fields = ["message", "personPosted", "topic", "hasMark"] + success_url = reverse_lazy("discussionBoard:topic") + + def get_form_kwargs(self): + kwargs = super(ReplyMixin, self).get_form_kwargs() + return kwargs + + def dispatch(self, request, *args, **kwargs): + topic_id = kwargs.get('topic_id', None) + topics = Topic.objects.filter(id = topic_id) + if not topics.exists(): + return HttpResponseRedirect(reverse("discussion_board:topics")) + self.topic = topics.first() + return super(ReplyMixin, self).dispatch(request, *args, **kwargs) + + + +class ReplyView(ReplyMixin, FormView): + model = Reply + template_name = 'ComSemApp/discussionBoard/reply_page.html' + context_object_name = 'replies' + fields = ["message", "personPosted", "topic", "hasMark"] + + def get_queryset(self): + return Reply.objects.filter(topic = self.topic) + + def get_context_data(self ,**kwargs): + self.object_list = self.get_queryset() + context = super(ReplyView, self).get_context_data(**kwargs) + context['topic_description'] = self.topic.topic + context['discussion_board'] = True + return context + + def form_invalid(self, reply_form, **kwargs): + response = super().form_invalid(reply_form) + return JsonResponse(reply_form.errors, status=400) + + def get(self, request, *args, **kwargs): + allow_empty = True + reply_form = ReplyForm() + reply_form.prefix = 'reply_form' + return self.render_to_response(self.get_context_data(form=reply_form)) + + def post(self, request, *args, **kwargs): + current_url = resolve(request.path_info).url_name + reply_form = ReplyForm(self.request.POST, prefix = 'reply_form') + likeButton = request.POST.get("like") + dislikeButton = request.POST.get("dislike") + if likeButton: + print(likeButton) + reply = Reply.objects.get(id = int(likeButton)) + reply.hasMark = 1 + reply.save() + if current_url == "admin_topic": + return HttpResponseRedirect(reverse("administrator:admin_topic", kwargs={'topic_id': self.topic.id })) + elif current_url == "teacher_topic": + return HttpResponseRedirect(reverse("teacher:teacher_topic", kwargs={'topic_id': self.topic.id })) + elif current_url == "student_topic": + return HttpResponseRedirect(reverse("student:student_topic", kwargs={'topic_id': self.topic.id })) + else: + return HttpResponseRedirect(reverse("discussion_board:topic", kwargs={'topic_id': self.topic.id })) + elif dislikeButton: + print(dislikeButton) + reply = Reply.objects.get(id = int(dislikeButton)) + reply.hasMark = 0 + reply.save() + if current_url == "admin_topic": + return HttpResponseRedirect(reverse("administrator:admin_topic", kwargs={'topic_id': self.topic.id })) + elif current_url == "teacher_topic": + return HttpResponseRedirect(reverse("teacher:teacher_topic", kwargs={'topic_id': self.topic.id })) + elif current_url == "student_topic": + return HttpResponseRedirect(reverse("student:student_topic", kwargs={'topic_id': self.topic.id })) + else: + return HttpResponseRedirect(reverse("discussion_board:topic", kwargs={'topic_id': self.topic.id })) + elif reply_form.is_valid(): + print("it is giving this if statement a thing") + reply = reply_form.save(commit=False) + reply.personPosted = request.user + reply.topic = self.topic + reply.hasMark = 0 + reply.save() + + if current_url == "admin_topic": + return HttpResponseRedirect(reverse("administrator:admin_topic", kwargs={'topic_id': self.topic.id })) + elif current_url == "teacher_topic": + return HttpResponseRedirect(reverse("teacher:teacher_topic", kwargs={'topic_id': self.topic.id })) + elif current_url == "student_topic": + return HttpResponseRedirect(reverse("student:student_topic", kwargs={'topic_id': self.topic.id })) + else: + return HttpResponseRedirect(reverse("discussion_board:topic", kwargs={'topic_id': self.topic.id })) + else: + return self.form_invalid(reply_form, **kwargs) + + + +class CreateThreadView(LoginRequiredMixin,FormView): + model = Reply + template_name = 'ComSemApp/discussionBoard/create_topic.html' + context_object_name = 'replies' + fields = ["message", "personPosted", "topic", "hasMark"] + + def form_invalid(self, topic_form, **kwargs): + response = super().form_invalid(form) + return JsonResponse(form.errors, status=400) + + def get(self, request, *args, **kwargs): + allow_empty = True + topic_form = TopicForm() + topic_form.prefix = "topic_form" + return self.render_to_response(self.get_context_data(form=topic_form)) + + def post(self, request, *args, **kwargs): + topic_form = TopicForm(self.request.POST, prefix = 'topic_form') + current_url = resolve(request.path_info).url_name + if topic_form.is_valid(): + topic = Topic(personPosted = request.user, topic = topic_form.cleaned_data["title"]) + topic.save() + reply = topic_form.save(commit=False) + reply.personPosted = request.user + reply.topic = topic + reply.hasMark = 0 + reply.save() + if current_url == "admin_create_topic": + return HttpResponseRedirect(reverse("administrator:admin_topic", kwargs={'topic_id': topic.id })) + elif current_url == "teacher_create_topic": + return HttpResponseRedirect(reverse("teacher:teacher_topic", kwargs={'topic_id': topic.id })) + elif current_url == "student_create_topic": + return HttpResponseRedirect(reverse("student:student_topic", kwargs={'topic_id': topic.id })) + else: + return HttpResponseRedirect(reverse("discussion_board:topic", kwargs={'topic_id': topic.id })) + else: + return self.form_invalid(topic_form, **kwargs) \ No newline at end of file diff --git a/ComSemApp/models.py b/ComSemApp/models.py index 0fe7674a..bd4fb206 100644 --- a/ComSemApp/models.py +++ b/ComSemApp/models.py @@ -163,13 +163,13 @@ class Worksheet(models.Model): course = models.ForeignKey('Course', on_delete=models.CASCADE, related_name='worksheets') created_by = models.ForeignKey('Teacher', null=True, on_delete=models.SET_NULL) topic = models.CharField(max_length=255, blank=True, null=True) - status = models.CharField(max_length=10, - choices=teacher_constants.WORKSHEET_STATUS_CHOICES, default=teacher_constants.WORKSHEET_STATUS_PENDING) + status = models.CharField(max_length=10, choices=teacher_constants.WORKSHEET_STATUS_CHOICES, default=teacher_constants.WORKSHEET_STATUS_PENDING) display_original = models.BooleanField(default=True) display_reformulation_text = models.BooleanField(default=True) display_reformulation_audio = models.BooleanField(default=True) display_all_expressions = models.BooleanField(default=False) - + autogen = models.NullBooleanField(default=False, null=True) + auto_student = models.ForeignKey('Student', null=True, on_delete=models.SET_NULL) objects = WorksheetManager() def __str__(self): @@ -309,3 +309,22 @@ def __str__(self): def frequency(self): words = Word.objects.filter(tag=self).all() return SequentialWords.objects.filter(word__in=words).count() + + +# Discussion Board + +#Topic model stores the person that posted it (which is a refrence to a user) +#and a string that is the topic title +class Topic(models.Model): + personPosted = models.ForeignKey(User, on_delete=models.CASCADE) + topic = models.CharField(max_length=255) + +#Each reply has a topic which is a reference to a topic object +#the personPosted which is a reference to a user +#the message is a string associated with a reply +#and hasMark is an integer associated with a mark (like a facebook like or dislike) +class Reply(models.Model): + topic = models.ForeignKey(Topic, on_delete=models.CASCADE) + personPosted = models.ForeignKey(User, on_delete=models.CASCADE) + message = models.CharField(max_length=1023) + hasMark = models.IntegerField() \ No newline at end of file diff --git a/ComSemApp/static/ComSemApp/DKNotus-Tour-master/DKNotusTour.png b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/DKNotusTour.png new file mode 100755 index 00000000..126344fe Binary files /dev/null and b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/DKNotusTour.png differ diff --git a/ComSemApp/static/ComSemApp/DKNotus-Tour-master/LICENSE b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/LICENSE new file mode 100755 index 00000000..5b132a5e --- /dev/null +++ b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 DK Notus IT Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ComSemApp/static/ComSemApp/DKNotus-Tour-master/README.md b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/README.md new file mode 100755 index 00000000..630e86e7 --- /dev/null +++ b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/README.md @@ -0,0 +1,235 @@ +# DK Notus Tour + +#### This compact solution for guided tours has 27 languages support. It requires only two very common dependencies: **jQuery** and **Bootstrap**. Also has useful features like auto scroll and "spotlight". We hope you enjoy it. + +![DK Notus Tour](DKNotusTour.png) + +We tried to keep all data regarding usage as short as possible. +## 1. Features + +Features that we considerble important: + + - **small requirements** - only jQuery and Bootstrap; + - **simple usage** - one function for common usage - yes, it's that simple; + - **events** - for advanced programmers usage; + - **scroll** - and some more useful features; + - **multi elements selection** - you can point more then one element for one tour step; + - **translations** - 27 languages support. + +## 2. Simple use case + +Lets start with two step tour for elements below: + +First of all we need to include two common libraries jQuery and Bootstrap. You ca use some CDN for that. + + + + +Then it's time tour library `dknotus-tour.js` or `dknotus-tour.min.js`. + + + +Finally we can define our own tour and run it with Tour.run(). Yes, it's that simple. + +```javascript + $(function(){ + $('#simpleBtn').click(function(){ + Tour.run([ + { + element: $('#btn1'), + content: 'first btn' + }, + { + element: $('#btn2'), + content: 'and the second one
description might be HTML' + }, + ]); + }); + }); +``` + +## 3. Different tour positions +```javascript + $(function(){ + $('#positionsBtn').click(function(){ + Tour.run([ + { + element: $('#posBtn'), + content: 'by default tour is on the right' + }, + { + element: $('#posBtn'), + content: 'but it can be on top', + position: 'top' + }, + { + element: $('#posBtn'), + content: 'bottom', + position: 'bottom' + }, + { + element: $('#posBtn'), + content: 'and finally on the left', + position: 'left' + } + ]); + }); + }); +``` + +## 4. Global and local parameters + +Tour may be run with two parameters: tour description (mandatory) and global options (optional) Tour.run(tourDescription, options). If for some tour hint some parameter is not set, then if it's possible it's taken from options. + +Possible parameters for hints descriptions and for global options: + +Parameter | Default value | Description +--------- | ------------- | ----------- +element | *none* | jQuery element (might be more then one), if it's not set then hint is skipped. +content | *empty string* | It's for contents of particular hints. +close | true | Defines if close button should be shown. +language | en | Defines interface language. Available languages: +|| en | English (default) +|| pl | Polish +|| be | Belarusian +|| ca | Catalan +|| cs | Czech +|| da | Danish +|| de | German +|| el | Greek +|| es | Spanish +|| et | Estonian +|| fi | Finnish +|| fr | French +|| hu | Hungarian +|| it | Italian +|| lt | Lithuanian +|| lv | Latvian +|| mk | Macedonian +|| nl | Dutch +|| no | Norwegian +|| pt | Portuguese +|| ru | Russian +|| sk | Slovak +|| sl | Slovenian +|| sq | Albanian +|| sv | Swedish +|| tr | Turkish +|| uk | Ukrainian +padding | 5 | Extra space around tour exposed elements. (Has only sense when spotlight option is true). +position | right | Determines where hint should be shown relatively to element it describes. +||| Possible values: right, left, top and bottom. +scroll | true | If true then scrolls window so selected element and hint would be as close as possible to the view center. +spotlight | true | If true then covers everything except selected element and hint with shadow. +forceCorrectionLeft | 0 | Useful if for some reason left offset needs to be modified. +forceCorrectionTop | 0 | Useful if for some reason top offset needs to be modified. +forceCorrectionWidth | 0 | Useful if for some reason width needs to be modified. +forceCorrectionHeight | 0 | Useful if for some reason height needs to be modified. + +All above options can be used for both: single hint description and for global options. With global options previous example can be written like: + +```javascript + $(function(){ + $('#positionsShorterBtn').click(function(){ + var globalOptions = { + element: $('#posBtn') + }; + + var tourDescription = [ + { + content: 'by default tour is on the right' + }, + { + content: 'but it can be on top', + position: 'top' + }, + { + content: 'bottom', + position: 'bottom' + }, + { + content: 'and finally on the left', + position: 'left' + } + ]; + + Tour.run(tourDescription, globalOptions); + }); + }); +``` + +## 5. Events example + +There are four events that can be used by developers: + + - **onstart()** - Triggered when new tour starts ( `Tour.run()` ); + - **onfinish()** - Triggered when Finish button is clicked; + - **onclose()** - Triggered when Close button is pressed ( `Tour.close()` ); + - **onstep( currentStep )** - Triggered on every step shown ( `Tour.next()` or `Tour.prev()` ); + - **onresize()** - By default this one is set. + +```javascript + $(function(){ + $('#eventsBtn').click(function(){ + Tour.onstart = function(){ + console.log('We started!'); + }; + + Tour.onfinish = function(){ + console.log('The End'); + }; + + Tour.onclose = function(){ + console.log('Tour interupted'); + }; + + Tour.onstep = function(currentStep){ + console.log('"That\'s one small step for a man ..."'); + console.log(currentStep); + }; + + Tour.run([ + { + element: $('#eventBtn1').add('#eventBtn3'), + content: 'You prefer photos?', + position: 'top' + }, + { + element: $('#eventBtn3').add('#eventBtn4'), + content: 'or videos?', + onstep: function(currentStep) { + console.log('Events defined in step, overwrites global definition'); + } + } + ]); + }); + }); +``` + +## 6. Tour interface + +#### Methods + +Method | Description +------ | ----------- +**Tour.run( tourDescription, globlOptions )** | Function for running Tour. +**Tour.next()** | Goes to next tour step. +**Tour.prev()** | Goes to previous tour step. +**Tour.close()** | Interrupts tour and closes it. +**Tour.current()** | Returns current step description. + +#### Events + +By default all except `onresize` are set to null. + +Event | Description +----- | ----------- +**Tour.onstart()** | Triggered when new tour starts ( Tour.run() ). +**Tour.onfinish()** | Triggered when Finish button is clicked. +**Tour.onclose()** | Triggered when Close button is pressed ( Tour.close() ). +**Tour.onstep( currentEvent )** | Triggered on every step shown ( Tour.next() or Tour.prev() ). +**Tour.onresize()** | By default this one is set. + +## 7. Contact + +Jan Doleczek diff --git a/ComSemApp/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.js b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.js new file mode 100755 index 00000000..d1d25fd5 --- /dev/null +++ b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.js @@ -0,0 +1,391 @@ +/*! + * DK Notus Tour JavaScript Library v1.2 + * https://github.com/DKNotusIT/DKNotus-Tour/ + * + * Copyright DK Notus and other contributors + * Released under the MIT license + * https://github.com/DKNotusIT/DKNotus-Tour/blob/master/LICENSE + * + * Date: 2018-03-17 + */ + +var Tour = (function() { + var t = [], + o, cur + T = { + step: { + pl: "krok", + en: "step", + be: "крок", + ca: "pas", + cs: "krok", + da: "trin", + de: "Schritt", + el: "βήμα", + es: "paso", + et: "samm", + fi: "vaihe", + fr: "étape", + hu: "lépés", + it: "passo", + lt: "žingsnis", + lv: "solis", + mk: "чекор", + nl: "stap", + no: "trinn", + pt: "passo", + ru: "шаг", + sk: "krok", + sl: "korak", + sq: "hapi", + sv: "steg", + tr: "adım", + uk: "крок" + }, + Next: { + pl: "Następny", + en: "Next", + be: "Далей", + ca: "Següent", + cs: "Další", + da: "Næste", + de: "Weiter", + el: "Την επόμενη", + es: "Siguiente", + et: "Järgmine", + fi: "Seuraava", + fr: "Prochaine", + hu: "Következő", + it: "Accanto", + lt: "Kitas", + lv: "Nākamā", + mk: "Следна", + nl: "Volgende", + no: "Neste", + pt: "Próximo", + ru: "Далее", + sk: "Ďalej", + sl: "Naprej", + sq: "Tjetër", + sv: "Nästa", + tr: "Gelecek", + uk: "Далі" + }, + Previous: { + pl: "Poprzedni", + en: "Previous", + be: "Папярэдні", + ca: "Anteriors", + cs: "Předchozí", + da: "Tidligere", + de: "Vorherige", + el: "Προηγούμενο", + es: "Anterior", + et: "Eelmine", + fi: "Edellinen", + fr: "Précédente", + hu: "Előző", + it: "Precedente", + lt: "Ankstesnis", + lv: "Iepriekšējā", + mk: "Претходна", + nl: "Vorige", + no: "Tidligere", + pt: "Anterior", + ru: "Предыдущий", + sk: "Predchádzajúce", + sl: "Prejšnji", + sq: "E mëparshme", + sv: "Föregående", + tr: "Önceki", + uk: "Попередній" + }, + Finish: { + pl: "Zakończ", + en: "Finish", + be: "Аздабленне", + ca: "Acabat", + cs: "Dokončit", + da: "Finish", + de: "Finish", + el: "Τελειώνει", + es: "Acabado", + et: "Lõpeta", + fi: "Loppuun", + fr: "Finition", + hu: "Befejezés", + it: "Finitura", + lt: "Apdaila", + lv: "Apdare", + mk: "Заврши", + nl: "Afwerking", + no: "Ferdig", + pt: "Acabamento", + ru: "Отделка", + sk: "Povrch", + sl: "Zaključek", + sq: "Finish", + sv: "Avsluta", + tr: "Bitir", + uk: "Оздоблення" + } + }; + + function _t(s) { + return T[s][t[cur].language] || T[s]['en']; + } + + function step(n) { + cur = n; + $('.tourStep, .tourBg').remove(); + + if (!t[n]) { + return; + } + + $('body').append([ + '
', + '
', + '
', + !t[n].close ? '' : '', + '
', + t[n].content, + '
', + '', + '
', + '
' + ].join('')); + + var el = $('.tourStep') + .addClass(t[n].position) + .css({ + minWidth: 250 + }), + x = 0, + y = 0; + + if (t[n].element && !!t[n].element.length) { + var x1 = 1e6, + y1 = 1e6, + x2 = 0, + y2 = 0; + + t[n].element.each(function(k, v) { + var ofs = $(v).offset(); + x1 = Math.min(x1, ofs.left + t[n].forceCorrectionLeft); + y1 = Math.min(y1, ofs.top + t[n].forceCorrectionTop); + + x2 = Math.max(x2, ofs.left + t[n].forceCorrectionLeft + t[n].forceCorrectionWidth + + parseInt($(v).css('border-left-width')) + + parseInt($(v).css('padding-left')) + + $(v).width() + + parseInt($(v).css('padding-right')) + + parseInt($(v).css('border-right-width')) + ); + + y2 = Math.max(y2, ofs.top + t[n].forceCorrectionTop + t[n].forceCorrectionHeight + + parseInt($(v).css('border-top-width')) + + parseInt($(v).css('padding-top')) + + $(v).height() + + parseInt($(v).css('padding-bottom')) + + parseInt($(v).css('border-bottom-width')) + ); + }); + + switch (t[n].position) { + case 'top': + y = y1 - el.height(); + x = ((x1 + x2) >> 1) - (el.width() >> 1); + break; + + case 'right': + y = ((y1 + y2) >> 1) - (el.height()>> 1); + x = x2; + break; + + case 'left': + y = ((y1 + y2) >> 1) - (el.height()>> 1); + x = x1 - el.width(); + break; + + case 'bottom': + y = y2; + x = ((x1 + x2) >> 1) - (el.width() >> 1); + break; + }; + }; + + el + .css({ + position: 'absolute', + left: x, + top: y + }) + .show(); + + if (t[n].spotlight) { + var p = t[n].padding; + $('body').append(Array(5).join('
')); + + var pos = [ + { + bottom: 'auto', + height: y1 - p + }, + { + top: y2 + p, + height: $(document).height() - y2 - p + }, + { + right: 'auto', + bottom: 'auto', + top: y1 - p, + width: x1 - p, + height: 2 * p + y2 - y1 + }, + { + left: x2 + p, + bottom: 'auto', + top: y1 - p, + height: 2 * p + y2 - y1 + } + ]; + + $('.tourBg') + .css({ + position: 'absolute', + zIndex: 1000, + top: 0, + bottom: 0, + right: 0, + left: 0, + background: '#000', + opacity: 0.3 + }).each(function(k, v){ + $(v).css(pos[k]); + }); + } + + if (!!t[n].scroll) { + var my = ((Math.min(y, y1) + Math.max(y + el.height(), y2)) >> 1) - ($(window).height() >> 1), + mx = ((Math.min(x, x1) + Math.max(x + el.width(), x2)) >> 1) - ($(window).width() >> 1); + + $('html, body').animate({ + scrollTop: Math.max(0, Math.min(y, y1, my)), + scrollLeft: Math.max(0, Math.min(x, x1, mx)) + }); + } + + if (!n) { + $('.tourPrev').remove(); + } + + if (n > t.length - 2) { + $('.tourNext').text(_t('Finish')); + } + + $('.tourStep') + .on('click', '.tourNext:not([disabled])', Tour.next) + .on('click', '.tourPrev:not([disabled])', Tour.prev) + .on('click', '.tourClose:not([disabled])', Tour.close); + + (t[n].onstep || Tour.onstep || function(){})(t[n]); + } + + $(window).on('resize', function() { + if (!!Tour.onresize) { + Tour.onresize(); + } + }); + + return { + run: function(tour, options) { + try { + t = []; + cur = 0; + + o = { + close: true, + content: '', + language: 'en', + padding: 5, + position: 'right', + scroll: true, + spotlight: true, + forceCorrectionLeft: 0, + forceCorrectionTop: 0, + forceCorrectionWidth: 0, + forceCorrectionHeight: 0, + onstep: null, + }; + + for (var k in options) { + o[k] = options[k]; + } + + $(tour).each(function(k, v) { + for (var kk in o) { + v[kk] = v[kk] || o[kk]; + }; + + if (v.element && !!v.element.length) { + t.push(v); + } + }); + + step(cur); + + if (!!Tour.onstart) { + Tour.onstart(); + } + } catch(e) {} + }, + + next: function() { + step(cur + 1); + + if (cur == t.length) { + if (!!Tour.onfinish) { + Tour.onfinish(); + } + } + }, + + prev: function(){ + step(cur - 1); + }, + + current: function(){ + return cur; + }, + + close: function(){ + step(-1); + + if (!!Tour.onclose) { + Tour.onclose(); + } + }, + + onstart: null, + onfinish: null, + onclose: null, + onstep: null, + + onresize: function() { + var n = cur - 1; + step(-1); + cur = n; + + setTimeout(function() { + Tour.next(); + }, 20); + } + }; +})(); diff --git a/ComSemApp/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.min.js b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.min.js new file mode 100755 index 00000000..38939c05 --- /dev/null +++ b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.min.js @@ -0,0 +1,12 @@ +/*! + * DK Notus Tour JavaScript Library v1.2 + * https://github.com/DKNotusIT/DKNotus-Tour/ + * + * Copyright DK Notus and other contributors + * Released under the MIT license + * https://github.com/DKNotusIT/DKNotus-Tour/blob/master/LICENSE + * + * Date: 2018-03-17 + */ + + var Tour=function(){function _t(s){return T[s][t[cur].language]||T[s].en}function step(n){if(cur=n,$(".tourStep, .tourBg").remove(),t[n]){$("body").append(['
','
','
',t[n].close?'':"",'
',t[n].content,"
",'","
","
"].join(""));var el=$(".tourStep").addClass(t[n].position).css({minWidth:250}),x=0,y=0;if(t[n].element&&t[n].element.length){var x1=1e6,y1=1e6,x2=0,y2=0;switch(t[n].element.each(function(k,v){var ofs=$(v).offset();x1=Math.min(x1,ofs.left+t[n].forceCorrectionLeft),y1=Math.min(y1,ofs.top+t[n].forceCorrectionTop),x2=Math.max(x2,ofs.left+t[n].forceCorrectionLeft+t[n].forceCorrectionWidth+parseInt($(v).css("border-left-width"))+parseInt($(v).css("padding-left"))+$(v).width()+parseInt($(v).css("padding-right"))+parseInt($(v).css("border-right-width"))),y2=Math.max(y2,ofs.top+t[n].forceCorrectionTop+t[n].forceCorrectionHeight+parseInt($(v).css("border-top-width"))+parseInt($(v).css("padding-top"))+$(v).height()+parseInt($(v).css("padding-bottom"))+parseInt($(v).css("border-bottom-width")))}),t[n].position){case"top":y=y1-el.height(),x=(x1+x2>>1)-(el.width()>>1);break;case"right":y=(y1+y2>>1)-(el.height()>>1),x=x2;break;case"left":y=(y1+y2>>1)-(el.height()>>1),x=x1-el.width();break;case"bottom":y=y2,x=(x1+x2>>1)-(el.width()>>1)}}if(el.css({position:"absolute",left:x,top:y}).show(),t[n].spotlight){var p=t[n].padding;$("body").append(Array(5).join('
'));var pos=[{bottom:"auto",height:y1-p},{top:y2+p,height:$(document).height()-y2-p},{right:"auto",bottom:"auto",top:y1-p,width:x1-p,height:2*p+y2-y1},{left:x2+p,bottom:"auto",top:y1-p,height:2*p+y2-y1}];$(".tourBg").css({position:"absolute",zIndex:1e3,top:0,bottom:0,right:0,left:0,background:"#000",opacity:.3}).each(function(k,v){$(v).css(pos[k])})}if(t[n].scroll){var my=(Math.min(y,y1)+Math.max(y+el.height(),y2)>>1)-($(window).height()>>1),mx=(Math.min(x,x1)+Math.max(x+el.width(),x2)>>1)-($(window).width()>>1);$("html, body").animate({scrollTop:Math.max(0,Math.min(y,y1,my)),scrollLeft:Math.max(0,Math.min(x,x1,mx))})}n||$(".tourPrev").remove(),n>t.length-2&&$(".tourNext").text(_t("Finish")),$(".tourStep").on("click",".tourNext:not([disabled])",Tour.next).on("click",".tourPrev:not([disabled])",Tour.prev).on("click",".tourClose:not([disabled])",Tour.close),(t[n].onstep||Tour.onstep||function(){})(t[n])}}var o,cur,t=[];return T={step:{pl:"krok",en:"step",be:"крок",ca:"pas",cs:"krok",da:"trin",de:"Schritt",el:"βήμα",es:"paso",et:"samm",fi:"vaihe",fr:"étape",hu:"lépés",it:"passo",lt:"žingsnis",lv:"solis",mk:"чекор",nl:"stap",no:"trinn",pt:"passo",ru:"шаг",sk:"krok",sl:"korak",sq:"hapi",sv:"steg",tr:"adım",uk:"крок"},Next:{pl:"Następny",en:"Next",be:"Далей",ca:"Següent",cs:"Další",da:"Næste",de:"Weiter",el:"Την επόμενη",es:"Siguiente",et:"Järgmine",fi:"Seuraava",fr:"Prochaine",hu:"Következő",it:"Accanto",lt:"Kitas",lv:"Nākamā",mk:"Следна",nl:"Volgende",no:"Neste",pt:"Próximo",ru:"Далее",sk:"Ďalej",sl:"Naprej",sq:"Tjetër",sv:"Nästa",tr:"Gelecek",uk:"Далі"},Previous:{pl:"Poprzedni",en:"Previous",be:"Папярэдні",ca:"Anteriors",cs:"Předchozí",da:"Tidligere",de:"Vorherige",el:"Προηγούμενο",es:"Anterior",et:"Eelmine",fi:"Edellinen",fr:"Précédente",hu:"Előző",it:"Precedente",lt:"Ankstesnis",lv:"Iepriekšējā",mk:"Претходна",nl:"Vorige",no:"Tidligere",pt:"Anterior",ru:"Предыдущий",sk:"Predchádzajúce",sl:"Prejšnji",sq:"E mëparshme",sv:"Föregående",tr:"Önceki",uk:"Попередній"},Finish:{pl:"Zakończ",en:"Finish",be:"Аздабленне",ca:"Acabat",cs:"Dokončit",da:"Finish",de:"Finish",el:"Τελειώνει",es:"Acabado",et:"Lõpeta",fi:"Loppuun",fr:"Finition",hu:"Befejezés",it:"Finitura",lt:"Apdaila",lv:"Apdare",mk:"Заврши",nl:"Afwerking",no:"Ferdig",pt:"Acabamento",ru:"Отделка",sk:"Povrch",sl:"Zaključek",sq:"Finish",sv:"Avsluta",tr:"Bitir",uk:"Оздоблення"}},$(window).on("resize",function(){Tour.onresize&&Tour.onresize()}),{run:function(tour,options){try{t=[],cur=0,o={close:!0,content:"",language:"en",padding:5,position:"right",scroll:!0,spotlight:!0,forceCorrectionLeft:0,forceCorrectionTop:0,forceCorrectionWidth:0,forceCorrectionHeight:0,onstep:null};for(var k in options)o[k]=options[k];$(tour).each(function(k,v){for(var kk in o)v[kk]=v[kk]||o[kk];v.element&&v.element.length&&t.push(v)}),step(cur),Tour.onstart&&Tour.onstart()}catch(e){}},next:function(){step(cur+1),cur==t.length&&Tour.onfinish&&Tour.onfinish()},prev:function(){step(cur-1)},current:function(){return cur},close:function(){step(-1),Tour.onclose&&Tour.onclose()},onstart:null,onfinish:null,onclose:null,onstep:null,onresize:function(){var n=cur-1;step(-1),cur=n,setTimeout(function(){Tour.next()},20)}}}(); \ No newline at end of file diff --git a/ComSemApp/static/ComSemApp/DKNotus-Tour-master/examples.html b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/examples.html new file mode 100755 index 00000000..52867654 --- /dev/null +++ b/ComSemApp/static/ComSemApp/DKNotus-Tour-master/examples.html @@ -0,0 +1,768 @@ + + + + + + DK Notus Tour - Examples + + + + + + +
+ + + + +
+
+

+ This compact solution for guided tours has + 27 languages support. + It requires only two very common dependencies: + jQuery and Bootstrap. + Also has useful features like auto scroll + and "spotlight". + We hope you enjoy it. +

+ +
+ DK Notus Tour +
+ +

+ We tried to keep all data regarding usage as short as possible. +

+ +

1. Features

+ +

+ Features that we considerble important: +

+ +
+
small requirements
+
only jQuery and Bootstrap;
+ +
simple usage
+
one function for common usage - yes, it's that simple;
+ +
events
+
for advanced programmers usage;
+ +
scroll
+
and some more useful features;
+ +
multi elem. selection
+
you can point more then one element for one tour step;
+ +
translations
+
languages support.
+
+ +

2. Simple use case

+ +

Lets start with two step tour for elements below:

+ + + + + +

+ First of all we need to include two common libraries + jQuery and Bootstrap. + You ca use some CDN for that. +

+ +
+
  <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
+  <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
+
+ +

+ Then it's time tour library dknotus-tour.js + or dknotus-tour.min.js. +

+ +
+
  <script src="dknotus-tour.js"></script>
+
+ +

+ Finally we can define our own tour and run it with + Tour.run(). Yes, it's that simple. +

+ + + +
+
+  $(function(){
+    $('#simpleBtn').click(function(){
+      Tour.run([
+        {
+          element: $('#btn1'),
+          content: 'first btn'
+        },
+        {
+          element: $('#btn2'),
+          content: 'and the second one<br>description might be <strong>HTML</strong>'
+        },
+      ]);
+    });
+  });
+            
+
+ +

3. Different tour positions

+ + + + + + + +
+
+  $(function(){
+    $('#positionsBtn').click(function(){
+      Tour.run([
+        {
+          element: $('#posBtn'),
+          content: 'by default tour is on the right'
+        },
+        {
+          element: $('#posBtn'),
+          content: 'but it can be on top',
+          position: 'top'
+        },
+        {
+          element: $('#posBtn'),
+          content: 'bottom',
+          position: 'bottom'
+        },
+        {
+          element: $('#posBtn'),
+          content: 'and finally on the left',
+          position: 'left'
+        }
+      ]);
+    });
+  });
+
+ +

4. Global and local parameters

+ +

+ Tour may be run with two parameters: tour description (mandatory) + and global options (optional) Tour.run(tourDescription, + options). If for some tour hint some parameter is not set, + then if it's possible it's taken from options. +

+ +

+ Possible parameters for hints descriptions and for global options +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterDefault valueDescription
elementnone + jQuery element (might be more then one), + if it's not set then hint is skipped. +
contentempty stringIt's for contents of particular hints.
closetrueDefines if close button should be shown.
languageen + Defines interface language. Available languages: +
+
en
+
English (default)
+ +
pl
+
Polish
+ +
be
+
Belarusian
+ +
ca
+
Catalan
+ +
cs
+
Czech
+ +
da
+
Danish
+ +
de
+
German
+ +
el
+
Greek
+ +
es
+
Spanish
+ +
et
+
Estonian
+ +
fi
+
Finnish
+ +
fr
+
French
+ +
hu
+
Hungarian
+ +
it
+
Italian
+ +
lt
+
Lithuanian
+ +
lv
+
Latvian
+ +
mk
+
Macedonian
+ +
nl
+
Dutch
+ +
no
+
Norwegian
+ +
pt
+
Portuguese
+ +
ru
+
Russian
+ +
sk
+
Slovak
+ +
sl
+
Slovenian
+ +
sq
+
Albanian
+ +
sv
+
Swedish
+ +
tr
+
Turkish
+ +
uk
+
Ukrainian
+
+
padding5 + Extra space around tour exposed elements. + (Has only sense when spotlight option is true). +
positionright + Determines where hint should be shown relativly to element + it describes.
+ Possible values: right, left, top and bottom. +
scrolltrue + If true then scrolls window so selected element + and hint would be as close as possible to the view center. +
spotlighttrue + If true then covers everything except selected element + and hint with shadow. +
forceCorrectionLeft0 + Useful if for some reason left offset needs to be modified. +
forceCorrectionTop0 + Useful if for some reason top offset needs to be modified. +
forceCorrectionWidth0 + Useful if for some reason width needs to be modified. +
forceCorrectionHeight0 + Useful if for some reason height needs to be modified. +
+ +

+ All above options can be used for both: single hint description + and for global options. With global options previous example + can be writen like: +

+ +
+
+  $(function(){
+    $('#positionsShorterBtn').click(function(){
+      var globalOptions = {
+        element: $('#posBtn')
+      };
+
+      var tourDescription = [
+        {
+          content: 'by default tour is on the right'
+        },
+        {
+          content: 'but it can be on top',
+          position: 'top'
+        },
+        {
+          content: 'bottom',
+          position: 'bottom'
+        },
+        {
+          content: 'and finally on the left',
+          position: 'left'
+        }
+      ];
+
+      Tour.run(tourDescription, globalOptions);
+    });
+  });
+
+ + + + + +

5. Events example

+ +

+ There are four events that can be used by developers: +

+ +
+
onstart()
+
Triggered when new tour starts ( Tour.run() ).
+ +
onfinish()
+
Triggered when Finish button is clicked.
+ +
onclose()
+
+ Triggered when Close button is pressed + ( Tour.close() ). +
+ +
onstep( currentStep )
+
+ Triggered on every step shown + ( Tour.next() or Tour.prev() ). +
+ +
onresize
+
By default this one is set.
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+ + + + + +
+
+  $(function(){
+    $('#eventsBtn').click(function(){
+      Tour.onstart = function(){
+        console.log('We started!');
+      };
+
+      Tour.onfinish = function(){
+        console.log('The End');
+      };
+
+      Tour.onclose = function(){
+        console.log('Tour interupted');
+      };
+
+      Tour.onstep = function(currentStep){
+        console.log('"That\'s one small step for a man ..."');
+        console.log(currentStep);
+      };
+
+      Tour.run([
+        {
+          element: $('#eventBtn1').add('#eventBtn3'),
+          content: 'You prefer photos?',
+          position: 'top'
+        },
+        {
+          element: $('#eventBtn3').add('#eventBtn4'),
+          content: 'or videos?',
+          onstep: function(currentStep) {
+            console.log('Events defined in step, overwrites global definition');
+          }
+        }
+      ]);
+    });
+  });
+
+ +

6. Tour interface

+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
Tour.run( tourDescription, globlOptions )Function for running Tour;
Tour.next()Goes to next tour step;
Tour.prev()Goes to previous tour step;
Tour.close()Interrupts tour and closes it;
Tour.current()Returns current step description.
+ +

Events

+ +

+ By default all except onresize are set to null. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDescription
Tour.onstart() + Triggered when new tour starts ( Tour.run() ); +
Tour.onfinish()Triggered when Finish button is clicked;
Tour.onclose() + Triggered when Close button is pressed + ( Tour.close() ); +
Tour.onstep( currentStep ) + Triggered on every step shown + ( Tour.next() or Tour.prev() ); +
Tour.onresize()By default this one is set.
+ +

7. Contact

+ + Jan Doleczek + +
+ + No animals were harmed during development. + + + + DK Notus 2016 + + +
+ + +
+
+ + diff --git a/ComSemApp/static/ComSemApp/Dyslexic/css/style.css b/ComSemApp/static/ComSemApp/Dyslexic/css/style.css new file mode 100644 index 00000000..fbb3a603 --- /dev/null +++ b/ComSemApp/static/ComSemApp/Dyslexic/css/style.css @@ -0,0 +1,39 @@ +@font-face{ + font-family: "Open-Dyslexic"; + src:url('../fonts/OpenDyslexic-Regular.eot'); + src:url('../fonts/OpenDyslexic-Regular.woff') format('woff'), + url('../fonts/OpenDyslexic-Regular.ttf') format('truetype'), + url('../fonts/OpenDyslexic-Regular.svg') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face{ + font-family: "Open-Dyslexic"; + src:url('../fonts/OpenDyslexic-Italic.eot'); + src:url('../fonts/OpenDyslexic-Italic.woff') format('woff'), + url('../fonts/OpenDyslexic-Italic.ttf') format('truetype'), + url('../fonts/OpenDyslexic-Italic.svg') format('svg'); + font-weight: normal; + font-style:italic; +} + +@font-face{ + font-family: "Open-Dyslexic"; + src:url('../fonts/OpenDyslexic-Bold.eot'); + src:url('../fonts/OpenDyslexic-Bold.woff') format('woff'), + url('../fonts/OpenDyslexic-Bold.ttf') format('truetype'), + url('../fonts/OpenDyslexic-Bold.svg') format('svg'); + font-weight: bold; + font-style: normal; +} + +@font-face{ + font-family: "Open-Dyslexic"; + src:url('../fonts/OpenDyslexic-BoldItalic.eot'); + src:url('../fonts/OpenDyslexic-BoldItalic.woff') format('woff'), + url('../fonts/OpenDyslexic-BoldItalic.ttf') format('truetype'), + url('../fonts/OpenDyslexic-BoldItalic.svg') format('svg'); + font-weight: bold; + font-style: italic; +} \ No newline at end of file diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.eot b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.eot new file mode 100644 index 00000000..03006b1b Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.eot differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.svg b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.svg new file mode 100644 index 00000000..b9b84815 --- /dev/null +++ b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.svg @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.ttf new file mode 100644 index 00000000..7c97eb43 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.woff b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.woff new file mode 100644 index 00000000..755476f6 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Bold.woff differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.eot b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.eot new file mode 100644 index 00000000..7eb8d251 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.eot differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.svg b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.svg new file mode 100644 index 00000000..238cb343 --- /dev/null +++ b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.svg @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.ttf new file mode 100644 index 00000000..f949334d Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.woff b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.woff new file mode 100644 index 00000000..624d0efd Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-BoldItalic.woff differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.eot b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.eot new file mode 100644 index 00000000..f34c1820 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.eot differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.svg b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.svg new file mode 100644 index 00000000..9fa245b9 --- /dev/null +++ b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.svg @@ -0,0 +1,459 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.ttf new file mode 100644 index 00000000..2f775808 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.woff b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.woff new file mode 100644 index 00000000..f6b12fc1 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Italic.woff differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.eot b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.eot new file mode 100644 index 00000000..0e4e9713 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.eot differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.svg b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.svg new file mode 100644 index 00000000..de1d9c81 --- /dev/null +++ b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.svg @@ -0,0 +1,616 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.ttf new file mode 100644 index 00000000..e7849348 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.woff b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.woff new file mode 100644 index 00000000..fdf9e37d Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic-Regular.woff differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic3-Bold.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic3-Bold.ttf new file mode 100644 index 00000000..395dffc3 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic3-Bold.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic3-Regular.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic3-Regular.ttf new file mode 100644 index 00000000..0ff4c0b5 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexic3-Regular.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-Bold.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-Bold.ttf new file mode 100644 index 00000000..e45fbff1 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-Bold.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-BoldItalic.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-BoldItalic.ttf new file mode 100644 index 00000000..df23fc39 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-BoldItalic.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-Italic.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-Italic.ttf new file mode 100644 index 00000000..a3dbb908 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-Italic.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-Regular.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-Regular.ttf new file mode 100644 index 00000000..edf6874c Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicAlta-Regular.ttf differ diff --git a/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicMono-Regular.ttf b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicMono-Regular.ttf new file mode 100644 index 00000000..65301ef4 Binary files /dev/null and b/ComSemApp/static/ComSemApp/Dyslexic/fonts/OpenDyslexicMono-Regular.ttf differ diff --git a/ComSemApp/student/urls.py b/ComSemApp/student/urls.py index ae073e67..9dc26cf6 100644 --- a/ComSemApp/student/urls.py +++ b/ComSemApp/student/urls.py @@ -1,9 +1,13 @@ from django.conf.urls import url from ComSemApp.student import views +from ComSemApp.discussionBoard import view as discussion_views app_name = 'student' urlpatterns = [ url(r'^$', views.CourseListView.as_view(), name='courses'), + + # url(r'^course/googleTranscribe/$', views.googleTranscribe, name="googleTranscribe"), + url(r'^course/(?P[0-9]+)/$', views.CourseDetailView.as_view(), name='course'), url(r'^course/(?P[0-9]+)/worksheet/(?P[0-9]+)/submission/list/$', views.SubmissionListView.as_view(), name='submission_list'), url(r'^course/(?P[0-9]+)/worksheet/(?P[0-9]+)/submission/create/$', views.SubmissionCreateView.as_view(), name='create_submission'), @@ -11,4 +15,7 @@ url(r'^course/(?P[0-9]+)/worksheet/(?P[0-9]+)/submission/(?P[0-9]+)/expressions/$', views.ExpressionListView.as_view(), name='worksheet_expression_list'), url(r'^course/(?P[0-9]+)/worksheet/(?P[0-9]+)/submission/(?P[0-9]+)/expression/(?P[0-9]+)/create/$', views.AttemptCreateView.as_view(), name='create_attempt'), url(r'^course/(?P[0-9]+)/worksheet/(?P[0-9]+)/submission/(?P[0-9]+)/attempt/(?P[0-9]+)/update/$', views.AttemptUpdateView.as_view(), name='update_attempt'), -] \ No newline at end of file + url(r'^discussion_board$', discussion_views.TopicListView.as_view(), name='student_discussion_board'), + url(r'^topic/(?P[0-9]+)/$', discussion_views.ReplyView.as_view(), name='student_topic'), + url(r'^newtopic/$', discussion_views.CreateThreadView.as_view(),name='student_create_topic') +] diff --git a/ComSemApp/student/views.py b/ComSemApp/student/views.py index 58085058..18e1192a 100644 --- a/ComSemApp/student/views.py +++ b/ComSemApp/student/views.py @@ -11,13 +11,15 @@ from django.http import JsonResponse, HttpResponseRedirect from django.urls import reverse from django.contrib import messages +from django.test import Client +from ComSemApp.teacher.constants import WORKSHEET_STATUS_PENDING, WORKSHEET_STATUS_UNRELEASED, WORKSHEET_STATUS_RELEASED +import datetime from ComSemApp.teacher import constants as teacher_constants from ComSemApp.models import * from ComSemApp.libs.mixins import RoleViewMixin, CourseViewMixin, WorksheetViewMixin, SubmissionViewMixin - class StudentViewMixin(RoleViewMixin): role_class = Student @@ -65,17 +67,193 @@ class CourseDetailView(StudentCourseViewMixin, DetailView): context_object_name = 'course' template_name = "ComSemApp/student/course.html" + def post(self, request, *args, **kwargs): #create Worksheets + self.generate_worksheet() + + + return HttpResponseRedirect(reverse("student:course", kwargs={"course_id": self.course.id})) + + + def db_create_expression(self, worksheet, student, expression): + + defaults = { + "worksheet": worksheet, + "expression": expression.expression, + "student": student, + "all_do": False, + "pronunciation": expression.pronunciation, + "context_vocabulary": expression.context_vocabulary, + "reformulation_text": expression.reformulation_text, + "audio": expression.audio, + } + return Expression.objects.create(**defaults) + + def create_worksheet(self, **kwargs): + course = kwargs.get("course") + if not course: + course = self.db_create_course() + + defaults = { + "course": course, + "topic": kwargs.get("topic", "TOPIC"), + "status": kwargs.get("status", WORKSHEET_STATUS_UNRELEASED), + "display_original": kwargs.get("display_original", True), + "display_reformulation_text": kwargs.get("display_reformulation_text", True), + "display_reformulation_audio": kwargs.get("display_reformulation_audio", True), + "display_all_expressions": kwargs.get("display_all_expressions", True), + "autogen": True, + "auto_student": self.student + } + + return Worksheet.objects.create(**defaults) + def get_object(self): return self.course + # Generates a practice worksheet for a student + def generate_worksheet(self, **kwargs): + + worksheets = self.course.worksheets + worksheets.filter(status=teacher_constants.WORKSHEET_STATUS_RELEASED) + expressions = "" + expressionList = [] + get_top = [] # Most attempted worksheets and attempts tuple + top_worksheets = [] # Most attempted worksheets + top_expressions = [] # Expressions from most attempted worksheets + + + + # make a list of worksheets with most attempts + for worksheet in worksheets.all(): + if worksheet.auto_student == self.student or worksheet.auto_student == None: + # get the last submission on the worksheet + # assign that submission to a variable, then run .get_number() on that + # keep track of the highest 3 worksheets + last_sub = worksheet.last_submission(self.student) + attempts = 0 + if last_sub: + attempts = last_sub.get_number() + + get_top.append((worksheet, attempts)) #str worksheet is the ID + + + + + + + # # Sort and keep top 3 most attempted worksheets + top_worksheets = sorted(get_top, key=lambda tup: tup[1], reverse=True)[:3] + top_worksheets = [i[0] for i in top_worksheets] + + + # Get expressions from top worksheets + for worksheet in top_worksheets: + print("HERHHERHEHEHR") + print(worksheet) + + # change current worksheet to a string to compare to the list + expression_filters = Q(Q(student=self.student) | Q(student=None) | Q(all_do=True) | Q(worksheet=worksheet)) + expressions = Expression.objects.filter(expression_filters) + + + + + # If current worksheet is in top list add it's expressions to top_expressions + for expression in expressions: + if expression.worksheet in top_worksheets: + top_expressions.append(expression) + + # create worksheet with unique name based on current time + + print("TOP EXPRESSIONS") + print(top_expressions) + + + + + current_time = datetime.datetime.now() + new_topic = str("Practice Worksheet " + current_time.strftime("%Y-%m-%d %H:%M:%S") + " for " + self.student.user.first_name + " " + self.student.user.last_name) + defaults = { + "course": self.course, + "topic": new_topic, + "status": kwargs.get("status", WORKSHEET_STATUS_UNRELEASED), + "display_original": kwargs.get("display_original", True), + "display_reformulation_text": kwargs.get("display_reformulation_text", True), + "display_reformulation_audio": kwargs.get("display_reformulation_audio", True), + "display_all_expressions": kwargs.get("display_all_expressions", True), + "auto_student": self.student, + "autogen": True + } + + self.create_worksheet(**defaults) + + # assign newly created worksheet to autogen_worksheet variable + for worksheet in self.course.worksheets.filter(status=teacher_constants.WORKSHEET_STATUS_UNRELEASED).all(): + print(worksheets) + topic_check = str(worksheet.topic) + if topic_check == new_topic: + autogen_worksheet = worksheet # New worksheet assigned to this variable + + for expression in top_expressions: + self.db_create_expression(autogen_worksheet, self.student, expression) + + # release the worksheet after giving it the new expressions + autogen_worksheet.release() + + return + + def get_context_data(self, **kwargs): context = super(CourseDetailView, self).get_context_data(**kwargs) - worksheets = self.course.worksheets.filter(status=teacher_constants.WORKSHEET_STATUS_RELEASED) + worksheets = self.course.worksheets.filter(Q(auto_student=self.student) | Q(auto_student=None), status=teacher_constants.WORKSHEET_STATUS_RELEASED ) - # TODO should this logic be in the worksheet model ? + + + + + expressionList = [] + + + + + context['complete'] = 0 + context['incomplete'] = 0 + context['ungraded']= 0 + context['expressionCount']= 0 + + # TODO should this logic be in the worksheet model ? -Zeke for worksheet in worksheets: + + expression_filters = Q(worksheet=worksheet) + if not worksheet.display_all_expressions: + expression_filters &= (Q(student=self.student) | Q(student=None) | Q(all_do=True) | Q(worksheet=worksheet)) + expressions = Expression.objects.filter(expression_filters) + last_submission = worksheet.last_submission(self.student) last_submission_status = last_submission.status if last_submission else "none" + + # Loop through and count status of worksheets/expressions + if last_submission_status == "incomplete" or last_submission_status == "none": + context['incomplete'] += 1 + for expression in expressions: + if expression.worksheet == worksheet: + expressionList.append(expression.expression) + if last_submission_status == "complete": + context['complete'] += 1 + for expression in expressions: + print('COMPLETE') + print(expression.expression) + if expression.worksheet == worksheet: + context['expressionCount'] += 1 + if last_submission_status == "ungraded": + context['ungraded'] += 1 + for expression in expressions: + if expression.worksheet == worksheet: + expressionList.append(expression.expression) + + + + last_submission_id = last_submission.id if last_submission else 0 status_colors = { "complete": "success", @@ -109,7 +287,9 @@ def get_context_data(self, **kwargs): worksheet.button_text = button_texts[last_submission_status] worksheet.link_url = link_urls[last_submission_status] + context['expressions'] = expressionList #list of expressions context['worksheets'] = worksheets + return context diff --git a/ComSemApp/teacher/urls.py b/ComSemApp/teacher/urls.py index 70cb2d59..bb448a29 100644 --- a/ComSemApp/teacher/urls.py +++ b/ComSemApp/teacher/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import url from ComSemApp.teacher import views from ComSemApp.corpus import views as corpus_views +from ComSemApp.discussionBoard import view as discussion_views app_name = 'teacher' urlpatterns = [ @@ -19,4 +20,7 @@ url(r'^course/(?P[0-9]+)/worksheet/(?P[0-9]+)/submission/(?P[0-9]+)/$', views.SubmissionView.as_view(), name='submission'), url(r'^corpus/search$', corpus_views.corpus_search, name='corpus_search'), + url(r'^discussion_board$', discussion_views.TopicListView.as_view(), name='teacher_discussion_board'), + url(r'^topic/(?P[0-9]+)/$', discussion_views.ReplyView.as_view(), name='teacher_topic'), + url(r'^newtopic/$', discussion_views.CreateThreadView.as_view(),name='teacher_create_topic') ] \ No newline at end of file diff --git a/ComSemApp/teacher/views.py b/ComSemApp/teacher/views.py index 569b3857..6e9ccd90 100644 --- a/ComSemApp/teacher/views.py +++ b/ComSemApp/teacher/views.py @@ -23,6 +23,7 @@ import json, math, datetime, os from ComSemApp.models import * +from django.template.defaulttags import register class TeacherViewMixin(RoleViewMixin): @@ -70,7 +71,56 @@ def get_context_data(self, **kwargs): class CourseDetailView(TeacherCourseViewMixin, DetailView): context_object_name = 'course' template_name = "ComSemApp/teacher/course.html" + + @register.filter('get_item') + def get_item(dictionary, key): + return dictionary.get(key) + def get_context_data(self, **kwargs): + data = super().get_context_data(**kwargs) + + worksheets = Worksheet.objects.filter(course=self.course) + worksheetsdict = {} + ungradedcountdict = {} + attemptsdict = {} + for student in self.course.students.all(): + worksheetcount = 0 + ungradedcount = 0 + attemptcount = 0 + submissions = StudentSubmission.objects.filter(student=student) + for submission in submissions : + if submission.worksheet.course == self.course: + if submission.status == 'ungraded': + ungradedcount = ungradedcount + 1 + + for worksheet in worksheets: + if worksheet.last_submission(student): + attemptcount = attemptcount + worksheet.last_submission(student).get_number() + if worksheet.auto_student == student or worksheet.auto_student == None: + worksheetcount = worksheetcount + 1 + + worksheetsdict[student.user.username] = worksheetcount + ungradedcountdict[student.user.username] = ungradedcount + attemptsdict[student.user.username] = attemptcount + + submissions = StudentSubmission.objects.filter(worksheet__course=self.course) + ungraded = submissions.filter(status="ungraded").count() + complete = submissions.filter(status="complete").count() + incomplete = submissions.filter(status="incomplete").count() + submissions = StudentSubmission.objects.all() + + + data['classungraded'] = ungraded + data['classincomplete'] = incomplete + data['classcomplete'] = complete + data['ungradedSubmissions'] = len(self.course.worksheets.all()) + data['worksheets'] = worksheetsdict + data['ungraded'] = ungradedcountdict + data['attempts'] = attemptsdict + print("data worksheets") + print(data['worksheets']) + + return data def get_object(self): return self.course diff --git a/ComSemApp/templates/ComSemApp/admin/student_list.html b/ComSemApp/templates/ComSemApp/admin/student_list.html index 17506d0e..17eb610e 100644 --- a/ComSemApp/templates/ComSemApp/admin/student_list.html +++ b/ComSemApp/templates/ComSemApp/admin/student_list.html @@ -8,12 +8,41 @@

All Students

- + + +
{% csrf_token %} + +
+ +
+ +
+
+

Uploader only allows CSV Files. +

+

+ Please order students in the following way: firstname, lastname, email, username. + Below is an example of how the first line would look, a comma indicates a new column. Names must contain letters only. +

+

+ John,Doe,johndoe@gmail.com,johnsusername +

+
+ +
+ {% if messages %} + {% for message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %}
{% if student_list %} diff --git a/ComSemApp/templates/ComSemApp/base.html b/ComSemApp/templates/ComSemApp/base.html index 01d06a97..4a77e2aa 100644 --- a/ComSemApp/templates/ComSemApp/base.html +++ b/ComSemApp/templates/ComSemApp/base.html @@ -14,6 +14,8 @@ + + {% with 'ComSemApp/Minton/css-'|add:minton_style|add:'/style.css' as minton_style_css %} {% endwith %} @@ -33,6 +35,7 @@ + diff --git a/ComSemApp/templates/ComSemApp/corpus/corpus_search.html b/ComSemApp/templates/ComSemApp/corpus/corpus_search.html index f2936d25..ca7d2cdf 100644 --- a/ComSemApp/templates/ComSemApp/corpus/corpus_search.html +++ b/ComSemApp/templates/ComSemApp/corpus/corpus_search.html @@ -1,7 +1,15 @@ + + {% extends 'ComSemApp/sidebar.html' %} {% block content %} +{% csrf_token %} +
@@ -15,212 +23,92 @@

+
+
- - - -
- -
- - - - - - - - - - - -
-
-
-

Add to Search Query

-
-
- -
-
-
- - -
-
-
- -
-
-
- - -
-
-
- -
-
-
- -
-
- -
-
-
- - - + +
+ + +
+
+ + +
+
+
+

+ Selector +

+
+
+
+
+

Please enter a word.

+
+
- - -
- -
- -
- - - - - - - - - - -
-
-
-

- Selector -

-
- -
- -
-
-
-
-
-
- -
-
- -
-
-
- -
- + +
+
+ +
+
+
+

+ Selector +

+
+
+
+
+
+
- - -
- -
- -
- - - - - - - - - - - -
- -
-
-

- Query Builder -

-
- -
- -
-
-
- - -
-
- - -
-
- -
- -
-
- - - - - - - - - - -
-
-
-
-
- - -
-
- -
-
- -
-
- -
- -
- -
-
-
- -
- -
+
+ + + +

+ + +

+ +
+ +

+

Offset determines how many spaces away from each other two words + can be in a sentence. For example, if you want to see "the" followed + by "king," you would set an offset of 1 (or -1 if "king" is entered for + the first word).

+

The offset indicates "up to and including," so for an offset value of 4, + two words may be shown together between 1 and 4 spaces away. Negative values + mean the second word will come first in the sentences.

-
+
+
@@ -232,21 +120,50 @@

- + if ($("#word1").val() !== "") { + collectData($("#word1").val()); + } else { + $(this).prop("disabled",false); + return; + } + + var sequential_search; + + if ($('#newOffset').val() !== "none") { + appendCriterion('offset', parseInt($('#newOffset').val()), []); + sequential_search = '1'; + } else { + sequential_search = '0'; + } + if ($("#word2").val() !== "") { + collectData($("#word2").val()); + } else { + collectTags(); + } + + var data = { + 'searchCriteria': $('#searchCriteria').val(), + 'searchType': sequential_search, + } + $('#searchResults').show().html("
Loading...
").load("{% url 'corpus:search_results' %}", data); + $(this).prop("disabled",false); + }); + }); + + {% endblock %} diff --git a/ComSemApp/templates/ComSemApp/corpus/tag_table.html b/ComSemApp/templates/ComSemApp/corpus/tag_table.html index 0a610528..0afbe48e 100644 --- a/ComSemApp/templates/ComSemApp/corpus/tag_table.html +++ b/ComSemApp/templates/ComSemApp/corpus/tag_table.html @@ -4,10 +4,6 @@ -
- - -
Tag Name Tag Type @@ -19,7 +15,7 @@
- +
diff --git a/ComSemApp/templates/ComSemApp/corpus/word_table.html b/ComSemApp/templates/ComSemApp/corpus/word_table.html index f597c7b8..92f02811 100644 --- a/ComSemApp/templates/ComSemApp/corpus/word_table.html +++ b/ComSemApp/templates/ComSemApp/corpus/word_table.html @@ -6,8 +6,8 @@
- - + +
Form @@ -22,8 +22,8 @@
- - + +
{{ word.form }} diff --git a/ComSemApp/templates/ComSemApp/course_table.html b/ComSemApp/templates/ComSemApp/course_table.html index 219e3a46..9fb87900 100644 --- a/ComSemApp/templates/ComSemApp/course_table.html +++ b/ComSemApp/templates/ComSemApp/course_table.html @@ -6,6 +6,9 @@

My Courses

+
+ +
diff --git a/ComSemApp/templates/ComSemApp/discussionBoard/add_reply.html b/ComSemApp/templates/ComSemApp/discussionBoard/add_reply.html new file mode 100644 index 00000000..5e7ec5cd --- /dev/null +++ b/ComSemApp/templates/ComSemApp/discussionBoard/add_reply.html @@ -0,0 +1,32 @@ + +
+
+
+ {% csrf_token %} + + {% for field in form %} +

+ {{ field }} + + {% if field.errors %} + {% for error in field.errors %} +

+ {% endfor %} + {% elif field.help_text %} + {{ field.help_text|safe }} + {% endif %} +

+ {% endfor %} +
+
+ +
+ +
+
+ +
+
+
\ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/discussionBoard/create_reply.html b/ComSemApp/templates/ComSemApp/discussionBoard/create_reply.html new file mode 100644 index 00000000..5c237b2e --- /dev/null +++ b/ComSemApp/templates/ComSemApp/discussionBoard/create_reply.html @@ -0,0 +1,44 @@ + +{% block content %} + + +
+ {% csrf_token %} + + +
+ +{% endblock %} \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/discussionBoard/create_topic.html b/ComSemApp/templates/ComSemApp/discussionBoard/create_topic.html new file mode 100644 index 00000000..3ac391d9 --- /dev/null +++ b/ComSemApp/templates/ComSemApp/discussionBoard/create_topic.html @@ -0,0 +1,44 @@ +{% extends 'ComSemApp/sidebar.html' %} +{% block content %} +
+
+
+

+ New Topic +

+
+
+
+
+
+ {% csrf_token %} + + {% for field in form %} +

+ {{ field.label_tag }}
+ {{ field }} + + {% if field.errors %} + {% for error in field.errors %} +

+ {% endfor %} + {% elif field.help_text %} + {{ field.help_text|safe }} + {% endif %} +

+ {% endfor %} +
+
+ +
+ +
+
+ +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/discussionBoard/reply_page.html b/ComSemApp/templates/ComSemApp/discussionBoard/reply_page.html new file mode 100644 index 00000000..3aaec034 --- /dev/null +++ b/ComSemApp/templates/ComSemApp/discussionBoard/reply_page.html @@ -0,0 +1,61 @@ +{% extends 'ComSemApp/sidebar.html' %} +{% block content %} +
+
+
+

+ {{topic_description}} +

+
+
+
+ {% if replies %} + + + + + + + + + + {% for reply in replies %} + + + + + + {% endfor %} + {% include 'ComSemApp/tablesorter_footer.html' %} + +
Person PostedMessageStarred
{{reply.personPosted}}{{reply.message}} + {% if reply.hasMark == 1 %} + + {% else %} + + {% endif %} + + {% if current_role %} + {% if current_role == "teacher" or current_role == "admin" %} +
+ {% csrf_token %} + {% if reply.hasMark == 1%} + + {% else %} + + {% endif %} +
+ + {% endif %} + {% endif %} +
+ {% else %} +

No available replies

+ {% endif %} +
+
+ {% include 'ComSemApp/discussionBoard/add_reply.html' %} +
+
+ +{% endblock %} diff --git a/ComSemApp/templates/ComSemApp/discussionBoard/topic_list.html b/ComSemApp/templates/ComSemApp/discussionBoard/topic_list.html new file mode 100644 index 00000000..81229e98 --- /dev/null +++ b/ComSemApp/templates/ComSemApp/discussionBoard/topic_list.html @@ -0,0 +1,65 @@ +{% extends 'ComSemApp/sidebar.html' %} +{% block content %} +
+
+
+

+ Topic List +

+
+
+ {% if current_role %} + {% if current_role == "teacher" %} + + + + {% elif current_role == "admin" %} + + + + {% elif current_role == "student" %} + + + + {% endif %} + {% endif %} +
+
+ +
+ {% if topics %} + + + + + + + + + + {% for topic in topics %} + + + + + + {% endfor %} + {% include 'ComSemApp/tablesorter_footer.html' %} + +
Person PostedTopic
{{topic.personPosted}}{{topic.topic}} + {% if current_role %} + {% if current_role == "teacher" %} + Go To Thread + {% elif current_role == "admin" %} + Go To Thread + {% elif current_role == "student" %} + Go To Thread + {% endif %} + {% endif %} +
+ {% else %} +

No available topics.

+ {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/messages.html b/ComSemApp/templates/ComSemApp/messages.html index a6c53c3f..8e672764 100644 --- a/ComSemApp/templates/ComSemApp/messages.html +++ b/ComSemApp/templates/ComSemApp/messages.html @@ -19,7 +19,6 @@ var cs_ajax_error = function(jqXHR, textStatus, errorThrown){ cs_notification('error', errorThrown) } - function cs_notification(tag, message){ $.Notification.notify(tag, 'right bottom', message) } @@ -28,4 +27,4 @@ - + \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/sidebar.html b/ComSemApp/templates/ComSemApp/sidebar.html index dbc3ed8b..c307d95b 100644 --- a/ComSemApp/templates/ComSemApp/sidebar.html +++ b/ComSemApp/templates/ComSemApp/sidebar.html @@ -1,7 +1,7 @@ {% extends 'ComSemApp/base.html' %} {% block body %} - +
@@ -114,7 +114,9 @@
Welcome, {{ user.username }}
Logout - + + This is an Error +
@@ -199,6 +201,14 @@
Welcome, {{ user.username }}
+ {% elif current_role == "teacher" %}
  • @@ -219,6 +229,14 @@
    Welcome, {{ user.username }}
+ {% elif current_role == "student" %}
  • @@ -228,7 +246,15 @@
    Welcome, {{ user.username }}
- {% endif %} + + {% endif %}
@@ -238,7 +264,7 @@
Welcome, {{ user.username }}
-
+
j
@@ -253,5 +279,25 @@
Welcome, {{ user.username }}
- -{% endblock %} + + +{% endblock %} \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/student/attempt_form.html b/ComSemApp/templates/ComSemApp/student/attempt_form.html index a4134b19..1a6e15c5 100644 --- a/ComSemApp/templates/ComSemApp/student/attempt_form.html +++ b/ComSemApp/templates/ComSemApp/student/attempt_form.html @@ -1,8 +1,7 @@ {% load static %} - - - + + @@ -11,6 +10,7 @@
+

{{ expression.expression }}

@@ -69,6 +69,11 @@
+
+ +

+
+
@@ -95,6 +100,61 @@ + + \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/student/course.html b/ComSemApp/templates/ComSemApp/student/course.html index 7a0b8849..ab124852 100644 --- a/ComSemApp/templates/ComSemApp/student/course.html +++ b/ComSemApp/templates/ComSemApp/student/course.html @@ -3,6 +3,94 @@ {% block content %}
+
+
+ +
+
+ +

Stats

+
+
+
+
+
+

{{ expressionCount }}

+
+
+

Expressions Completed

+
+
+
+
+
+
+

{{ complete }}

+
+
+

Worksheets Completed

+
+
+
+
+
+
+
+
+

{{ incomplete }}

+
+
+

Worksheets Incomplete

+
+
+
+
+
+
+

{{ ungraded }}

+
+
+

Worksheets to Grade

+
+
+
+
+
+
+
+ +
+
+

Worksheet Status

+ +
+
+
+ +
+
+
+

Assigned Expressions

+ +
+ + + + + + {% for expression in expressions %} + + + + {% endfor %} + +
{{ expression }}
+
+
+
+
+
+
{% include 'ComSemApp/course_details.html' %} @@ -13,6 +101,11 @@

Worksheets

+
+
{% csrf_token %} + +
+
{% if worksheets %} @@ -43,6 +136,7 @@

Worksheets

{% include 'ComSemApp/tablesorter_footer.html'%}
+ {% else %}

No worksheets available

{% endif %} @@ -50,4 +144,44 @@

Worksheets

+ +
+ + +
+ {% endblock %} diff --git a/ComSemApp/templates/ComSemApp/student/create_submission.html b/ComSemApp/templates/ComSemApp/student/create_submission.html index 2c9412ac..8b874abe 100644 --- a/ComSemApp/templates/ComSemApp/student/create_submission.html +++ b/ComSemApp/templates/ComSemApp/student/create_submission.html @@ -13,10 +13,10 @@

Create Submission

- +
{% csrf_token %} - +
@@ -94,7 +94,6 @@

- + {% endblock %} diff --git a/ComSemApp/templates/ComSemApp/student/expression_list.html b/ComSemApp/templates/ComSemApp/student/expression_list.html index e982aa9f..766ff784 100644 --- a/ComSemApp/templates/ComSemApp/student/expression_list.html +++ b/ComSemApp/templates/ComSemApp/student/expression_list.html @@ -52,7 +52,8 @@ populateEditor($(this).attr('url')); }); }) - + + {% else %}

This worksheet has no expressions.
-{% endif %} +{% endif %} \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/teacher/course.html b/ComSemApp/templates/ComSemApp/teacher/course.html index e2544458..7b25a027 100644 --- a/ComSemApp/templates/ComSemApp/teacher/course.html +++ b/ComSemApp/templates/ComSemApp/teacher/course.html @@ -5,6 +5,7 @@
+

Course Information

@@ -16,7 +17,23 @@

Course Information

- {% include 'ComSemApp/course_details.html' %} +
+ {% include 'ComSemApp/course_details.html' %} +
+ +
+
+ +

Submission Status

+ +
+ + +
+
+
+
+
@@ -38,13 +55,60 @@

Students

- {% include 'ComSemApp/teacher/course_students.html' %} + {% include 'ComSemApp/teacher/course_students_detail.html' %}
+
+
+
+
+
+
+
+
+
+ +
@@ -60,7 +124,7 @@

Students

Worksheets

- +
@@ -144,4 +208,16 @@

Worksheets

+ + + {% endblock %} diff --git a/ComSemApp/templates/ComSemApp/teacher/course_students.html b/ComSemApp/templates/ComSemApp/teacher/course_students.html index fb91437d..78be64c9 100644 --- a/ComSemApp/templates/ComSemApp/teacher/course_students.html +++ b/ComSemApp/templates/ComSemApp/teacher/course_students.html @@ -1,3 +1,4 @@ + {% if course.students.all %} diff --git a/ComSemApp/templates/ComSemApp/teacher/course_students_detail.html b/ComSemApp/templates/ComSemApp/teacher/course_students_detail.html new file mode 100644 index 00000000..12d1fb8f --- /dev/null +++ b/ComSemApp/templates/ComSemApp/teacher/course_students_detail.html @@ -0,0 +1,32 @@ +{% load static %} +{% if course.students.all %} + + + Name + Worksheets Assigned + Ungraded Submissions + Number of Attempts + + + + + + {% for student in course.students.all %} + + {{student}} + + {{ worksheets|get_item:student.user.username }} + + + {{ ungraded|get_item:student.user.username }} + + + {{ attempts|get_item:student.user.username }} + + Email + + {% endfor %} + +{% else %} +

No Enrolled Students

+{% endif %} \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/teacher/edit_worksheet.html b/ComSemApp/templates/ComSemApp/teacher/edit_worksheet.html index e7e85282..5158346f 100644 --- a/ComSemApp/templates/ComSemApp/teacher/edit_worksheet.html +++ b/ComSemApp/templates/ComSemApp/teacher/edit_worksheet.html @@ -4,8 +4,11 @@ {% block content %} + +
+ {% csrf_token %} @@ -32,8 +35,8 @@

-

Worksheet Info

+

Worksheet Info

@@ -44,7 +47,7 @@

Worksheet Info

-
+
@@ -73,7 +76,6 @@

Worksheet Info

-
@@ -81,9 +83,8 @@

Worksheet Info

- +
-
+ + {% endblock %} diff --git a/ComSemApp/templates/ComSemApp/teacher/expression_form.html b/ComSemApp/templates/ComSemApp/teacher/expression_form.html index d8e268a6..9c57666b 100644 --- a/ComSemApp/templates/ComSemApp/teacher/expression_form.html +++ b/ComSemApp/templates/ComSemApp/teacher/expression_form.html @@ -1,11 +1,12 @@ {% load static %} - - + + + - @@ -82,6 +84,12 @@

+
+ +

+
+
+
{% include 'ComSemApp/audio_recording.html' %}
@@ -99,7 +107,7 @@

- +
@@ -107,54 +115,130 @@

- inspectFormData(worksheetFormData) - - $.ajax({ - type: "POST", - url: $("#create_or_update_url").val(), - data: worksheetFormData, - processData: false, - contentType: false, - success: function(response){ - cs_notification('success', "Expression Saved") - // update expressions table - drawExpressionsTable(); + + { + element: $('#contextVocabulary'), + content: 'Enter Context\/Vocab Here' + }, + { + element: $('#pronunciation'), + content: 'Enter Pronunciation Here' + }, + { + element: $('#reformulation'), + content: 'Enter Reformulation Here' + }, + { + element: $('#display_reformulation_audioRow'), + content: 'Click the RED button to record your reformulation. Click the GREEN button to stop recording.' + }, + { + element: $('#saveExpression'), + content: 'Be sure to click here to SAVE your worksheet item.' + }, + ]); + } + \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/teacher/expressions.html b/ComSemApp/templates/ComSemApp/teacher/expressions.html index 32e5d75f..1f087031 100644 --- a/ComSemApp/templates/ComSemApp/teacher/expressions.html +++ b/ComSemApp/templates/ComSemApp/teacher/expressions.html @@ -10,7 +10,9 @@ + {% for expression in expressions %} + {{ forloop.counter }} @@ -29,10 +31,10 @@ {{ expression.expression }} - - @@ -66,3 +68,18 @@ {% else %}

No expressions have been created for this worksheet yet.

{% endif %} + + \ No newline at end of file diff --git a/ComSemApp/templates/ComSemApp/teacher/worksheet_list.html b/ComSemApp/templates/ComSemApp/teacher/worksheet_list.html index 90f40a2e..2caba98f 100644 --- a/ComSemApp/templates/ComSemApp/teacher/worksheet_list.html +++ b/ComSemApp/templates/ComSemApp/teacher/worksheet_list.html @@ -42,4 +42,4 @@ {% else %}

No worksheets have been created yet. Click here to create one.

-{% endif %} +{% endif %} \ No newline at end of file diff --git a/ComSemApp/templates/registration/login.html b/ComSemApp/templates/registration/login.html index d7c17bd3..c4c9d4cf 100644 --- a/ComSemApp/templates/registration/login.html +++ b/ComSemApp/templates/registration/login.html @@ -60,14 +60,16 @@ Forgot your password?
-
-
+ + diff --git a/CommunicationSeminar-7d71fc1430af.json b/CommunicationSeminar-7d71fc1430af.json new file mode 100644 index 00000000..92aa5f78 --- /dev/null +++ b/CommunicationSeminar-7d71fc1430af.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "communicationsem-1551739563072", + "private_key_id": "7d71fc1430afbbc39e7c036244c1030b6d67442c", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDY7VjU3PReWgam\nJPvYcotxezUpw3ot1wSDNCzYb/jpI6U8COeicsBMxd3Tw4xtNwSIqRFTJjR5VQBV\nmk1UaKQcDFFicBZgNq4GMGSxpy62dxpsRP8jaclGd5Vw5LicA2duvslYMML41nfx\nL9i10z62lzOrY4/QjMDdV+5FCq1ijYZWwx4UzVPEQRugBZ/XarxCQnn6+IC9TyCT\nnLCQ/WJ+r9jUDYX4XeAegWs0cp9IqN9bTFwWGkRpKAHxuKJKjNjvuPwKfYKe4g2k\ntHN2xP0p2o5vSpE08QxCY83XO3kZBgTLBAPXdC3D1O62m1Ex8XLGB3QmL0+dl/CT\n+F44tL5vAgMBAAECggEAHm8uR1m4mdmHlUfFuFhZt/zROKAe7s975+x/XjOszbNy\nkI8EmzCo9McjQtKXS7tW/NVmLi7uAxfUiYNcgLstoGxvU/WQg28oezXO6sh0hU+w\neqreINnL+KxOYHMLyNexa0LZJ3EFPw7X776xaNElEKOmBtBbx6aiaGuDF6yn0Wuc\n4OFPlBFWFQyW3nwyQ2VxlAOhExa9D5qszahzhRfsZQIh78JnStrEXct1GdAzIgl8\n+0UVI98VgZ1Y/jXQsC9qjpSZjFvbxmDSitdKEfzu519UciW5wTUvzcrV2KDX4hP9\nry91NHr+EnLK9pbUsDem5KO5bHWsAB4EMOoBFaPGAQKBgQDvQtWUkAG9QnW7Mr0+\nNj76U5jpuYMyvKaRfenYlM6B/5Lsjmj6aWfKRUTy0aSUUjKYlfWsZmQGkyRYbWct\n//NYqE6wsTi3neVYB+PWp7cckMvAShGvlhtoHiQ3TZcDMN2fg+9lHQopjGxF3Ljb\nPmYCw81i7PnsYCZyi94OvhqQbQKBgQDoGoMOROsVJCuQhuLwyX6gQh5I8OgVsjAV\nYuqk0uF4ozzNx03HLhZUbBl+edJvIepspv0iXqqL7FtsKjd/tOq5Wwokasnph+ma\nQyYn1fygoGITvzjY0JvZhAbDSSjAqln/nus3kK6yd/qM+GyKi0G8b9CxJOPtUyQo\nFapwwEIYywKBgDo57JpL9XBmlRoqfe1+7BKld4ghsnENggfUOa2QgTUXylyxR5Dx\nR+Xst5vUMxY5bsC9dd0vydXjxyuR7FNAf6sqwoOzjVb6IQ8sVGEvzZsxDbTNEcLG\naAMNmsiupDqNkNG6HO9hgtCbsNnLnl0UsWSo2/ngK+4/c/vaV/8Eeph9AoGBAJIC\n+6ciW/D0zCMjd+iaGXqfnYWtZ5MegXkyvQHrV8QYjMu3MYq3x3xcdL1HzcXzxBGO\nC3hvrUJQ8kJdXISOP4tTZlH6jALJaX+N9pbg2wgBXTGm/hAHS8POdqKrh2OMxwkz\n5C0Ua5ICQDAmp8GQUU5F2TdXSbumrfw5hPthNzN5AoGAYFby81jrjNmL1+2qFzch\nAOstKnE8Tfsf70EX6mpITqd+AVe4lCIzPnWsRlY/xSO5mFBcGqfpZlKqyIzj49kK\nkdTVKAR7xFesKT7Xgv8KYAYSbZXLBp/z8FUwCvrxXfcLd321LIf0tSkoySrcw3BG\nwWweesIuRbUDKXlv/hZzpaA=\n-----END PRIVATE KEY-----\n", + "client_email": "starting-account-9jnnspij26oc@communicationsem-1551739563072.iam.gserviceaccount.com", + "client_id": "109876849879407886090", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/starting-account-9jnnspij26oc%40communicationsem-1551739563072.iam.gserviceaccount.com" +} diff --git a/CommunicationSeminar/settings/production.py b/CommunicationSeminar/settings/production.py index b8a6c59c..793a0e3e 100644 --- a/CommunicationSeminar/settings/production.py +++ b/CommunicationSeminar/settings/production.py @@ -33,6 +33,12 @@ 'comsempython.us-east-2.elasticbeanstalk.com', 'localhost', '.comsem.net', + 'comsem.localhost.run', + 'group08.localhost.run', + 'group081.localhost.run', + 'group082.localhost.run', + 'group083.localhost.run', + 'group088.localhost.run', ] # Application definition @@ -46,6 +52,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'django_select2', + 'django_smoke_tests', ] MIDDLEWARE = [ diff --git a/GoogleSpeech_Start.sh b/GoogleSpeech_Start.sh new file mode 100755 index 00000000..fd6bc353 --- /dev/null +++ b/GoogleSpeech_Start.sh @@ -0,0 +1 @@ +export GOOGLE_APPLICATION_CREDENTIALS="CommunicationSeminar-7d71fc1430af.json" diff --git a/googletest.py b/googletest.py new file mode 100644 index 00000000..e3526346 --- /dev/null +++ b/googletest.py @@ -0,0 +1,33 @@ +import io +import os + +# Imports the Google Cloud client library +from google.cloud import speech +from google.cloud.speech import enums +from google.cloud.speech import types + +# Instantiates a client +client = speech.SpeechClient() + +# The name of the audio file to transcribe +file_name = "efs/reformulations/41c87965-0174-4231-8253-db494f9a8a61.ogg" + +# Loads the audio into memory +with io.open(file_name, 'rb') as audio_file: + content = audio_file.read() + +audio = types.RecognitionAudio(content=content) + +# for i in range(8000, 48001, 2000): +for i in [8000, 12000, 16000, 24000, 48000]: + print(i) + config = types.RecognitionConfig( + encoding=enums.RecognitionConfig.AudioEncoding.OGG_OPUS, + sample_rate_hertz=i, + language_code='en-US') + + # Detects speech in the audio file + response = client.recognize(config, audio) + print("Recognizing") + for result in response.results: + print('Transcript: {}'.format(result.alternatives[0].transcript)) diff --git a/static/ComSemApp/DKNotus-Tour-master/DKNotusTour.png b/static/ComSemApp/DKNotus-Tour-master/DKNotusTour.png new file mode 100644 index 00000000..126344fe Binary files /dev/null and b/static/ComSemApp/DKNotus-Tour-master/DKNotusTour.png differ diff --git a/static/ComSemApp/DKNotus-Tour-master/LICENSE b/static/ComSemApp/DKNotus-Tour-master/LICENSE new file mode 100644 index 00000000..5b132a5e --- /dev/null +++ b/static/ComSemApp/DKNotus-Tour-master/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 DK Notus IT Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/static/ComSemApp/DKNotus-Tour-master/README.md b/static/ComSemApp/DKNotus-Tour-master/README.md new file mode 100644 index 00000000..630e86e7 --- /dev/null +++ b/static/ComSemApp/DKNotus-Tour-master/README.md @@ -0,0 +1,235 @@ +# DK Notus Tour + +#### This compact solution for guided tours has 27 languages support. It requires only two very common dependencies: **jQuery** and **Bootstrap**. Also has useful features like auto scroll and "spotlight". We hope you enjoy it. + +![DK Notus Tour](DKNotusTour.png) + +We tried to keep all data regarding usage as short as possible. +## 1. Features + +Features that we considerble important: + + - **small requirements** - only jQuery and Bootstrap; + - **simple usage** - one function for common usage - yes, it's that simple; + - **events** - for advanced programmers usage; + - **scroll** - and some more useful features; + - **multi elements selection** - you can point more then one element for one tour step; + - **translations** - 27 languages support. + +## 2. Simple use case + +Lets start with two step tour for elements below: + +First of all we need to include two common libraries jQuery and Bootstrap. You ca use some CDN for that. + + + + +Then it's time tour library `dknotus-tour.js` or `dknotus-tour.min.js`. + + + +Finally we can define our own tour and run it with Tour.run(). Yes, it's that simple. + +```javascript + $(function(){ + $('#simpleBtn').click(function(){ + Tour.run([ + { + element: $('#btn1'), + content: 'first btn' + }, + { + element: $('#btn2'), + content: 'and the second one
description might be HTML' + }, + ]); + }); + }); +``` + +## 3. Different tour positions +```javascript + $(function(){ + $('#positionsBtn').click(function(){ + Tour.run([ + { + element: $('#posBtn'), + content: 'by default tour is on the right' + }, + { + element: $('#posBtn'), + content: 'but it can be on top', + position: 'top' + }, + { + element: $('#posBtn'), + content: 'bottom', + position: 'bottom' + }, + { + element: $('#posBtn'), + content: 'and finally on the left', + position: 'left' + } + ]); + }); + }); +``` + +## 4. Global and local parameters + +Tour may be run with two parameters: tour description (mandatory) and global options (optional) Tour.run(tourDescription, options). If for some tour hint some parameter is not set, then if it's possible it's taken from options. + +Possible parameters for hints descriptions and for global options: + +Parameter | Default value | Description +--------- | ------------- | ----------- +element | *none* | jQuery element (might be more then one), if it's not set then hint is skipped. +content | *empty string* | It's for contents of particular hints. +close | true | Defines if close button should be shown. +language | en | Defines interface language. Available languages: +|| en | English (default) +|| pl | Polish +|| be | Belarusian +|| ca | Catalan +|| cs | Czech +|| da | Danish +|| de | German +|| el | Greek +|| es | Spanish +|| et | Estonian +|| fi | Finnish +|| fr | French +|| hu | Hungarian +|| it | Italian +|| lt | Lithuanian +|| lv | Latvian +|| mk | Macedonian +|| nl | Dutch +|| no | Norwegian +|| pt | Portuguese +|| ru | Russian +|| sk | Slovak +|| sl | Slovenian +|| sq | Albanian +|| sv | Swedish +|| tr | Turkish +|| uk | Ukrainian +padding | 5 | Extra space around tour exposed elements. (Has only sense when spotlight option is true). +position | right | Determines where hint should be shown relatively to element it describes. +||| Possible values: right, left, top and bottom. +scroll | true | If true then scrolls window so selected element and hint would be as close as possible to the view center. +spotlight | true | If true then covers everything except selected element and hint with shadow. +forceCorrectionLeft | 0 | Useful if for some reason left offset needs to be modified. +forceCorrectionTop | 0 | Useful if for some reason top offset needs to be modified. +forceCorrectionWidth | 0 | Useful if for some reason width needs to be modified. +forceCorrectionHeight | 0 | Useful if for some reason height needs to be modified. + +All above options can be used for both: single hint description and for global options. With global options previous example can be written like: + +```javascript + $(function(){ + $('#positionsShorterBtn').click(function(){ + var globalOptions = { + element: $('#posBtn') + }; + + var tourDescription = [ + { + content: 'by default tour is on the right' + }, + { + content: 'but it can be on top', + position: 'top' + }, + { + content: 'bottom', + position: 'bottom' + }, + { + content: 'and finally on the left', + position: 'left' + } + ]; + + Tour.run(tourDescription, globalOptions); + }); + }); +``` + +## 5. Events example + +There are four events that can be used by developers: + + - **onstart()** - Triggered when new tour starts ( `Tour.run()` ); + - **onfinish()** - Triggered when Finish button is clicked; + - **onclose()** - Triggered when Close button is pressed ( `Tour.close()` ); + - **onstep( currentStep )** - Triggered on every step shown ( `Tour.next()` or `Tour.prev()` ); + - **onresize()** - By default this one is set. + +```javascript + $(function(){ + $('#eventsBtn').click(function(){ + Tour.onstart = function(){ + console.log('We started!'); + }; + + Tour.onfinish = function(){ + console.log('The End'); + }; + + Tour.onclose = function(){ + console.log('Tour interupted'); + }; + + Tour.onstep = function(currentStep){ + console.log('"That\'s one small step for a man ..."'); + console.log(currentStep); + }; + + Tour.run([ + { + element: $('#eventBtn1').add('#eventBtn3'), + content: 'You prefer photos?', + position: 'top' + }, + { + element: $('#eventBtn3').add('#eventBtn4'), + content: 'or videos?', + onstep: function(currentStep) { + console.log('Events defined in step, overwrites global definition'); + } + } + ]); + }); + }); +``` + +## 6. Tour interface + +#### Methods + +Method | Description +------ | ----------- +**Tour.run( tourDescription, globlOptions )** | Function for running Tour. +**Tour.next()** | Goes to next tour step. +**Tour.prev()** | Goes to previous tour step. +**Tour.close()** | Interrupts tour and closes it. +**Tour.current()** | Returns current step description. + +#### Events + +By default all except `onresize` are set to null. + +Event | Description +----- | ----------- +**Tour.onstart()** | Triggered when new tour starts ( Tour.run() ). +**Tour.onfinish()** | Triggered when Finish button is clicked. +**Tour.onclose()** | Triggered when Close button is pressed ( Tour.close() ). +**Tour.onstep( currentEvent )** | Triggered on every step shown ( Tour.next() or Tour.prev() ). +**Tour.onresize()** | By default this one is set. + +## 7. Contact + +Jan Doleczek diff --git a/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.js b/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.js new file mode 100644 index 00000000..d1d25fd5 --- /dev/null +++ b/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.js @@ -0,0 +1,391 @@ +/*! + * DK Notus Tour JavaScript Library v1.2 + * https://github.com/DKNotusIT/DKNotus-Tour/ + * + * Copyright DK Notus and other contributors + * Released under the MIT license + * https://github.com/DKNotusIT/DKNotus-Tour/blob/master/LICENSE + * + * Date: 2018-03-17 + */ + +var Tour = (function() { + var t = [], + o, cur + T = { + step: { + pl: "krok", + en: "step", + be: "крок", + ca: "pas", + cs: "krok", + da: "trin", + de: "Schritt", + el: "βήμα", + es: "paso", + et: "samm", + fi: "vaihe", + fr: "étape", + hu: "lépés", + it: "passo", + lt: "žingsnis", + lv: "solis", + mk: "чекор", + nl: "stap", + no: "trinn", + pt: "passo", + ru: "шаг", + sk: "krok", + sl: "korak", + sq: "hapi", + sv: "steg", + tr: "adım", + uk: "крок" + }, + Next: { + pl: "Następny", + en: "Next", + be: "Далей", + ca: "Següent", + cs: "Další", + da: "Næste", + de: "Weiter", + el: "Την επόμενη", + es: "Siguiente", + et: "Järgmine", + fi: "Seuraava", + fr: "Prochaine", + hu: "Következő", + it: "Accanto", + lt: "Kitas", + lv: "Nākamā", + mk: "Следна", + nl: "Volgende", + no: "Neste", + pt: "Próximo", + ru: "Далее", + sk: "Ďalej", + sl: "Naprej", + sq: "Tjetër", + sv: "Nästa", + tr: "Gelecek", + uk: "Далі" + }, + Previous: { + pl: "Poprzedni", + en: "Previous", + be: "Папярэдні", + ca: "Anteriors", + cs: "Předchozí", + da: "Tidligere", + de: "Vorherige", + el: "Προηγούμενο", + es: "Anterior", + et: "Eelmine", + fi: "Edellinen", + fr: "Précédente", + hu: "Előző", + it: "Precedente", + lt: "Ankstesnis", + lv: "Iepriekšējā", + mk: "Претходна", + nl: "Vorige", + no: "Tidligere", + pt: "Anterior", + ru: "Предыдущий", + sk: "Predchádzajúce", + sl: "Prejšnji", + sq: "E mëparshme", + sv: "Föregående", + tr: "Önceki", + uk: "Попередній" + }, + Finish: { + pl: "Zakończ", + en: "Finish", + be: "Аздабленне", + ca: "Acabat", + cs: "Dokončit", + da: "Finish", + de: "Finish", + el: "Τελειώνει", + es: "Acabado", + et: "Lõpeta", + fi: "Loppuun", + fr: "Finition", + hu: "Befejezés", + it: "Finitura", + lt: "Apdaila", + lv: "Apdare", + mk: "Заврши", + nl: "Afwerking", + no: "Ferdig", + pt: "Acabamento", + ru: "Отделка", + sk: "Povrch", + sl: "Zaključek", + sq: "Finish", + sv: "Avsluta", + tr: "Bitir", + uk: "Оздоблення" + } + }; + + function _t(s) { + return T[s][t[cur].language] || T[s]['en']; + } + + function step(n) { + cur = n; + $('.tourStep, .tourBg').remove(); + + if (!t[n]) { + return; + } + + $('body').append([ + '
', + '
', + '
', + !t[n].close ? '' : '', + '
', + t[n].content, + '
', + '', + '
', + '
' + ].join('')); + + var el = $('.tourStep') + .addClass(t[n].position) + .css({ + minWidth: 250 + }), + x = 0, + y = 0; + + if (t[n].element && !!t[n].element.length) { + var x1 = 1e6, + y1 = 1e6, + x2 = 0, + y2 = 0; + + t[n].element.each(function(k, v) { + var ofs = $(v).offset(); + x1 = Math.min(x1, ofs.left + t[n].forceCorrectionLeft); + y1 = Math.min(y1, ofs.top + t[n].forceCorrectionTop); + + x2 = Math.max(x2, ofs.left + t[n].forceCorrectionLeft + t[n].forceCorrectionWidth + + parseInt($(v).css('border-left-width')) + + parseInt($(v).css('padding-left')) + + $(v).width() + + parseInt($(v).css('padding-right')) + + parseInt($(v).css('border-right-width')) + ); + + y2 = Math.max(y2, ofs.top + t[n].forceCorrectionTop + t[n].forceCorrectionHeight + + parseInt($(v).css('border-top-width')) + + parseInt($(v).css('padding-top')) + + $(v).height() + + parseInt($(v).css('padding-bottom')) + + parseInt($(v).css('border-bottom-width')) + ); + }); + + switch (t[n].position) { + case 'top': + y = y1 - el.height(); + x = ((x1 + x2) >> 1) - (el.width() >> 1); + break; + + case 'right': + y = ((y1 + y2) >> 1) - (el.height()>> 1); + x = x2; + break; + + case 'left': + y = ((y1 + y2) >> 1) - (el.height()>> 1); + x = x1 - el.width(); + break; + + case 'bottom': + y = y2; + x = ((x1 + x2) >> 1) - (el.width() >> 1); + break; + }; + }; + + el + .css({ + position: 'absolute', + left: x, + top: y + }) + .show(); + + if (t[n].spotlight) { + var p = t[n].padding; + $('body').append(Array(5).join('
')); + + var pos = [ + { + bottom: 'auto', + height: y1 - p + }, + { + top: y2 + p, + height: $(document).height() - y2 - p + }, + { + right: 'auto', + bottom: 'auto', + top: y1 - p, + width: x1 - p, + height: 2 * p + y2 - y1 + }, + { + left: x2 + p, + bottom: 'auto', + top: y1 - p, + height: 2 * p + y2 - y1 + } + ]; + + $('.tourBg') + .css({ + position: 'absolute', + zIndex: 1000, + top: 0, + bottom: 0, + right: 0, + left: 0, + background: '#000', + opacity: 0.3 + }).each(function(k, v){ + $(v).css(pos[k]); + }); + } + + if (!!t[n].scroll) { + var my = ((Math.min(y, y1) + Math.max(y + el.height(), y2)) >> 1) - ($(window).height() >> 1), + mx = ((Math.min(x, x1) + Math.max(x + el.width(), x2)) >> 1) - ($(window).width() >> 1); + + $('html, body').animate({ + scrollTop: Math.max(0, Math.min(y, y1, my)), + scrollLeft: Math.max(0, Math.min(x, x1, mx)) + }); + } + + if (!n) { + $('.tourPrev').remove(); + } + + if (n > t.length - 2) { + $('.tourNext').text(_t('Finish')); + } + + $('.tourStep') + .on('click', '.tourNext:not([disabled])', Tour.next) + .on('click', '.tourPrev:not([disabled])', Tour.prev) + .on('click', '.tourClose:not([disabled])', Tour.close); + + (t[n].onstep || Tour.onstep || function(){})(t[n]); + } + + $(window).on('resize', function() { + if (!!Tour.onresize) { + Tour.onresize(); + } + }); + + return { + run: function(tour, options) { + try { + t = []; + cur = 0; + + o = { + close: true, + content: '', + language: 'en', + padding: 5, + position: 'right', + scroll: true, + spotlight: true, + forceCorrectionLeft: 0, + forceCorrectionTop: 0, + forceCorrectionWidth: 0, + forceCorrectionHeight: 0, + onstep: null, + }; + + for (var k in options) { + o[k] = options[k]; + } + + $(tour).each(function(k, v) { + for (var kk in o) { + v[kk] = v[kk] || o[kk]; + }; + + if (v.element && !!v.element.length) { + t.push(v); + } + }); + + step(cur); + + if (!!Tour.onstart) { + Tour.onstart(); + } + } catch(e) {} + }, + + next: function() { + step(cur + 1); + + if (cur == t.length) { + if (!!Tour.onfinish) { + Tour.onfinish(); + } + } + }, + + prev: function(){ + step(cur - 1); + }, + + current: function(){ + return cur; + }, + + close: function(){ + step(-1); + + if (!!Tour.onclose) { + Tour.onclose(); + } + }, + + onstart: null, + onfinish: null, + onclose: null, + onstep: null, + + onresize: function() { + var n = cur - 1; + step(-1); + cur = n; + + setTimeout(function() { + Tour.next(); + }, 20); + } + }; +})(); diff --git a/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.min.js b/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.min.js new file mode 100644 index 00000000..38939c05 --- /dev/null +++ b/static/ComSemApp/DKNotus-Tour-master/dknotus-tour.min.js @@ -0,0 +1,12 @@ +/*! + * DK Notus Tour JavaScript Library v1.2 + * https://github.com/DKNotusIT/DKNotus-Tour/ + * + * Copyright DK Notus and other contributors + * Released under the MIT license + * https://github.com/DKNotusIT/DKNotus-Tour/blob/master/LICENSE + * + * Date: 2018-03-17 + */ + + var Tour=function(){function _t(s){return T[s][t[cur].language]||T[s].en}function step(n){if(cur=n,$(".tourStep, .tourBg").remove(),t[n]){$("body").append(['
','
','
',t[n].close?'':"",'
',t[n].content,"
",'","
","
"].join(""));var el=$(".tourStep").addClass(t[n].position).css({minWidth:250}),x=0,y=0;if(t[n].element&&t[n].element.length){var x1=1e6,y1=1e6,x2=0,y2=0;switch(t[n].element.each(function(k,v){var ofs=$(v).offset();x1=Math.min(x1,ofs.left+t[n].forceCorrectionLeft),y1=Math.min(y1,ofs.top+t[n].forceCorrectionTop),x2=Math.max(x2,ofs.left+t[n].forceCorrectionLeft+t[n].forceCorrectionWidth+parseInt($(v).css("border-left-width"))+parseInt($(v).css("padding-left"))+$(v).width()+parseInt($(v).css("padding-right"))+parseInt($(v).css("border-right-width"))),y2=Math.max(y2,ofs.top+t[n].forceCorrectionTop+t[n].forceCorrectionHeight+parseInt($(v).css("border-top-width"))+parseInt($(v).css("padding-top"))+$(v).height()+parseInt($(v).css("padding-bottom"))+parseInt($(v).css("border-bottom-width")))}),t[n].position){case"top":y=y1-el.height(),x=(x1+x2>>1)-(el.width()>>1);break;case"right":y=(y1+y2>>1)-(el.height()>>1),x=x2;break;case"left":y=(y1+y2>>1)-(el.height()>>1),x=x1-el.width();break;case"bottom":y=y2,x=(x1+x2>>1)-(el.width()>>1)}}if(el.css({position:"absolute",left:x,top:y}).show(),t[n].spotlight){var p=t[n].padding;$("body").append(Array(5).join('
'));var pos=[{bottom:"auto",height:y1-p},{top:y2+p,height:$(document).height()-y2-p},{right:"auto",bottom:"auto",top:y1-p,width:x1-p,height:2*p+y2-y1},{left:x2+p,bottom:"auto",top:y1-p,height:2*p+y2-y1}];$(".tourBg").css({position:"absolute",zIndex:1e3,top:0,bottom:0,right:0,left:0,background:"#000",opacity:.3}).each(function(k,v){$(v).css(pos[k])})}if(t[n].scroll){var my=(Math.min(y,y1)+Math.max(y+el.height(),y2)>>1)-($(window).height()>>1),mx=(Math.min(x,x1)+Math.max(x+el.width(),x2)>>1)-($(window).width()>>1);$("html, body").animate({scrollTop:Math.max(0,Math.min(y,y1,my)),scrollLeft:Math.max(0,Math.min(x,x1,mx))})}n||$(".tourPrev").remove(),n>t.length-2&&$(".tourNext").text(_t("Finish")),$(".tourStep").on("click",".tourNext:not([disabled])",Tour.next).on("click",".tourPrev:not([disabled])",Tour.prev).on("click",".tourClose:not([disabled])",Tour.close),(t[n].onstep||Tour.onstep||function(){})(t[n])}}var o,cur,t=[];return T={step:{pl:"krok",en:"step",be:"крок",ca:"pas",cs:"krok",da:"trin",de:"Schritt",el:"βήμα",es:"paso",et:"samm",fi:"vaihe",fr:"étape",hu:"lépés",it:"passo",lt:"žingsnis",lv:"solis",mk:"чекор",nl:"stap",no:"trinn",pt:"passo",ru:"шаг",sk:"krok",sl:"korak",sq:"hapi",sv:"steg",tr:"adım",uk:"крок"},Next:{pl:"Następny",en:"Next",be:"Далей",ca:"Següent",cs:"Další",da:"Næste",de:"Weiter",el:"Την επόμενη",es:"Siguiente",et:"Järgmine",fi:"Seuraava",fr:"Prochaine",hu:"Következő",it:"Accanto",lt:"Kitas",lv:"Nākamā",mk:"Следна",nl:"Volgende",no:"Neste",pt:"Próximo",ru:"Далее",sk:"Ďalej",sl:"Naprej",sq:"Tjetër",sv:"Nästa",tr:"Gelecek",uk:"Далі"},Previous:{pl:"Poprzedni",en:"Previous",be:"Папярэдні",ca:"Anteriors",cs:"Předchozí",da:"Tidligere",de:"Vorherige",el:"Προηγούμενο",es:"Anterior",et:"Eelmine",fi:"Edellinen",fr:"Précédente",hu:"Előző",it:"Precedente",lt:"Ankstesnis",lv:"Iepriekšējā",mk:"Претходна",nl:"Vorige",no:"Tidligere",pt:"Anterior",ru:"Предыдущий",sk:"Predchádzajúce",sl:"Prejšnji",sq:"E mëparshme",sv:"Föregående",tr:"Önceki",uk:"Попередній"},Finish:{pl:"Zakończ",en:"Finish",be:"Аздабленне",ca:"Acabat",cs:"Dokončit",da:"Finish",de:"Finish",el:"Τελειώνει",es:"Acabado",et:"Lõpeta",fi:"Loppuun",fr:"Finition",hu:"Befejezés",it:"Finitura",lt:"Apdaila",lv:"Apdare",mk:"Заврши",nl:"Afwerking",no:"Ferdig",pt:"Acabamento",ru:"Отделка",sk:"Povrch",sl:"Zaključek",sq:"Finish",sv:"Avsluta",tr:"Bitir",uk:"Оздоблення"}},$(window).on("resize",function(){Tour.onresize&&Tour.onresize()}),{run:function(tour,options){try{t=[],cur=0,o={close:!0,content:"",language:"en",padding:5,position:"right",scroll:!0,spotlight:!0,forceCorrectionLeft:0,forceCorrectionTop:0,forceCorrectionWidth:0,forceCorrectionHeight:0,onstep:null};for(var k in options)o[k]=options[k];$(tour).each(function(k,v){for(var kk in o)v[kk]=v[kk]||o[kk];v.element&&v.element.length&&t.push(v)}),step(cur),Tour.onstart&&Tour.onstart()}catch(e){}},next:function(){step(cur+1),cur==t.length&&Tour.onfinish&&Tour.onfinish()},prev:function(){step(cur-1)},current:function(){return cur},close:function(){step(-1),Tour.onclose&&Tour.onclose()},onstart:null,onfinish:null,onclose:null,onstep:null,onresize:function(){var n=cur-1;step(-1),cur=n,setTimeout(function(){Tour.next()},20)}}}(); \ No newline at end of file diff --git a/static/ComSemApp/DKNotus-Tour-master/examples.html b/static/ComSemApp/DKNotus-Tour-master/examples.html new file mode 100644 index 00000000..52867654 --- /dev/null +++ b/static/ComSemApp/DKNotus-Tour-master/examples.html @@ -0,0 +1,768 @@ + + + + + + DK Notus Tour - Examples + + + + + + +
+ + + + +
+
+

+ This compact solution for guided tours has + 27 languages support. + It requires only two very common dependencies: + jQuery and Bootstrap. + Also has useful features like auto scroll + and "spotlight". + We hope you enjoy it. +

+ +
+ DK Notus Tour +
+ +

+ We tried to keep all data regarding usage as short as possible. +

+ +

1. Features

+ +

+ Features that we considerble important: +

+ +
+
small requirements
+
only jQuery and Bootstrap;
+ +
simple usage
+
one function for common usage - yes, it's that simple;
+ +
events
+
for advanced programmers usage;
+ +
scroll
+
and some more useful features;
+ +
multi elem. selection
+
you can point more then one element for one tour step;
+ +
translations
+
languages support.
+
+ +

2. Simple use case

+ +

Lets start with two step tour for elements below:

+ + + + + +

+ First of all we need to include two common libraries + jQuery and Bootstrap. + You ca use some CDN for that. +

+ +
+
  <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
+  <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
+
+ +

+ Then it's time tour library dknotus-tour.js + or dknotus-tour.min.js. +

+ +
+
  <script src="dknotus-tour.js"></script>
+
+ +

+ Finally we can define our own tour and run it with + Tour.run(). Yes, it's that simple. +

+ + + +
+
+  $(function(){
+    $('#simpleBtn').click(function(){
+      Tour.run([
+        {
+          element: $('#btn1'),
+          content: 'first btn'
+        },
+        {
+          element: $('#btn2'),
+          content: 'and the second one<br>description might be <strong>HTML</strong>'
+        },
+      ]);
+    });
+  });
+            
+
+ +

3. Different tour positions

+ + + + + + + +
+
+  $(function(){
+    $('#positionsBtn').click(function(){
+      Tour.run([
+        {
+          element: $('#posBtn'),
+          content: 'by default tour is on the right'
+        },
+        {
+          element: $('#posBtn'),
+          content: 'but it can be on top',
+          position: 'top'
+        },
+        {
+          element: $('#posBtn'),
+          content: 'bottom',
+          position: 'bottom'
+        },
+        {
+          element: $('#posBtn'),
+          content: 'and finally on the left',
+          position: 'left'
+        }
+      ]);
+    });
+  });
+
+ +

4. Global and local parameters

+ +

+ Tour may be run with two parameters: tour description (mandatory) + and global options (optional) Tour.run(tourDescription, + options). If for some tour hint some parameter is not set, + then if it's possible it's taken from options. +

+ +

+ Possible parameters for hints descriptions and for global options +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterDefault valueDescription
elementnone + jQuery element (might be more then one), + if it's not set then hint is skipped. +
contentempty stringIt's for contents of particular hints.
closetrueDefines if close button should be shown.
languageen + Defines interface language. Available languages: +
+
en
+
English (default)
+ +
pl
+
Polish
+ +
be
+
Belarusian
+ +
ca
+
Catalan
+ +
cs
+
Czech
+ +
da
+
Danish
+ +
de
+
German
+ +
el
+
Greek
+ +
es
+
Spanish
+ +
et
+
Estonian
+ +
fi
+
Finnish
+ +
fr
+
French
+ +
hu
+
Hungarian
+ +
it
+
Italian
+ +
lt
+
Lithuanian
+ +
lv
+
Latvian
+ +
mk
+
Macedonian
+ +
nl
+
Dutch
+ +
no
+
Norwegian
+ +
pt
+
Portuguese
+ +
ru
+
Russian
+ +
sk
+
Slovak
+ +
sl
+
Slovenian
+ +
sq
+
Albanian
+ +
sv
+
Swedish
+ +
tr
+
Turkish
+ +
uk
+
Ukrainian
+
+
padding5 + Extra space around tour exposed elements. + (Has only sense when spotlight option is true). +
positionright + Determines where hint should be shown relativly to element + it describes.
+ Possible values: right, left, top and bottom. +
scrolltrue + If true then scrolls window so selected element + and hint would be as close as possible to the view center. +
spotlighttrue + If true then covers everything except selected element + and hint with shadow. +
forceCorrectionLeft0 + Useful if for some reason left offset needs to be modified. +
forceCorrectionTop0 + Useful if for some reason top offset needs to be modified. +
forceCorrectionWidth0 + Useful if for some reason width needs to be modified. +
forceCorrectionHeight0 + Useful if for some reason height needs to be modified. +
+ +

+ All above options can be used for both: single hint description + and for global options. With global options previous example + can be writen like: +

+ +
+
+  $(function(){
+    $('#positionsShorterBtn').click(function(){
+      var globalOptions = {
+        element: $('#posBtn')
+      };
+
+      var tourDescription = [
+        {
+          content: 'by default tour is on the right'
+        },
+        {
+          content: 'but it can be on top',
+          position: 'top'
+        },
+        {
+          content: 'bottom',
+          position: 'bottom'
+        },
+        {
+          content: 'and finally on the left',
+          position: 'left'
+        }
+      ];
+
+      Tour.run(tourDescription, globalOptions);
+    });
+  });
+
+ + + + + +

5. Events example

+ +

+ There are four events that can be used by developers: +

+ +
+
onstart()
+
Triggered when new tour starts ( Tour.run() ).
+ +
onfinish()
+
Triggered when Finish button is clicked.
+ +
onclose()
+
+ Triggered when Close button is pressed + ( Tour.close() ). +
+ +
onstep( currentStep )
+
+ Triggered on every step shown + ( Tour.next() or Tour.prev() ). +
+ +
onresize
+
By default this one is set.
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+ + + + + +
+
+  $(function(){
+    $('#eventsBtn').click(function(){
+      Tour.onstart = function(){
+        console.log('We started!');
+      };
+
+      Tour.onfinish = function(){
+        console.log('The End');
+      };
+
+      Tour.onclose = function(){
+        console.log('Tour interupted');
+      };
+
+      Tour.onstep = function(currentStep){
+        console.log('"That\'s one small step for a man ..."');
+        console.log(currentStep);
+      };
+
+      Tour.run([
+        {
+          element: $('#eventBtn1').add('#eventBtn3'),
+          content: 'You prefer photos?',
+          position: 'top'
+        },
+        {
+          element: $('#eventBtn3').add('#eventBtn4'),
+          content: 'or videos?',
+          onstep: function(currentStep) {
+            console.log('Events defined in step, overwrites global definition');
+          }
+        }
+      ]);
+    });
+  });
+
+ +

6. Tour interface

+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
Tour.run( tourDescription, globlOptions )Function for running Tour;
Tour.next()Goes to next tour step;
Tour.prev()Goes to previous tour step;
Tour.close()Interrupts tour and closes it;
Tour.current()Returns current step description.
+ +

Events

+ +

+ By default all except onresize are set to null. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDescription
Tour.onstart() + Triggered when new tour starts ( Tour.run() ); +
Tour.onfinish()Triggered when Finish button is clicked;
Tour.onclose() + Triggered when Close button is pressed + ( Tour.close() ); +
Tour.onstep( currentStep ) + Triggered on every step shown + ( Tour.next() or Tour.prev() ); +
Tour.onresize()By default this one is set.
+ +

7. Contact

+ + Jan Doleczek + +
+ + No animals were harmed during development. + + + + DK Notus 2016 + + +
+ + +
+
+ +