Merge pull request #1 from setton/master

Initial implementation of the frontend
This commit is contained in:
Nicolas Setton
2017-09-04 11:45:55 -04:00
committed by GitHub
19 changed files with 360 additions and 40 deletions

View File

@@ -1,2 +1,37 @@
# langserv
prototype server for creating interactive "try SPARK / try Ada" webpages
# code_examples_server
Prototype server for creating interactive "try SPARK / try Ada" webpages
## Getting started
To setup, do this:
```sh
# This is to create the virtualenv and install Python stuff
virtualenv env
source env/bin/activate
pip install -r REQUIREMENTS.txt
# This is to initialize the django database
./manage.py makemigrations
./manage.py migrate
# This is to get the ACE editor
cd compile_server/app/static
git clone https://github.com/ajaxorg/ace-builds.git
```
To enter the environment, to this
```sh
source env/bin/activate
```
To enter some examples in the database, do this:
```sh
./manage.py fill_examples --dir=resources/example/a
```
To launch the server, do this:
```sh
./manage.py runserver
```

2
REQUIREMENTS.txt Normal file
View File

@@ -0,0 +1,2 @@
django
djangorestframework

View File

@@ -1,24 +1,68 @@
# The manage.py command to enter examples in the database.
# This is meant to be used by the administrators of the project only.
import glob
import os
import yaml
from django.core.management.base import BaseCommand
from compile_server.app.models import Resource, Example
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--remove_all', const=True, default=False,
action='store_const',
action='store_const',
help='remove all examples from the database')
parser.add_argument('--dirs', nargs='+', type=str,
help='add the examples found in the given dirs')
parser.add_argument('--dir', nargs=1, type=str,
help='add the example found in the given dir')
def handle(self, *args, **options):
if 'remove_all' in
if options.get('remove_all', False):
# Remove all examples from the database
Resource.objects.all().delete()
Example.objects.all().delete()
#where = options['add_dir']
if options.get('dir', None):
d = options.get('dir')[0]
#if not os.path.isdir(where):
# print "Pass a directory to --dir"
# For now, consider all files in the directory to be part of the
# example
if not os.path.isdir(d):
print "{} is not a valid directory".format(d)
return
# Look for 'example.yaml'
example_yaml = os.path.join(d, 'example.yaml')
if not os.path.isfile(example_yaml):
print 'There is no "example.yaml" in {}'.format(d)
return
# Check contents of example.yaml
with open(example_yaml, 'rb') as f:
try:
metadata = yaml.load(f.read())
except:
print format_traceback
print 'Could not decode yaml in {}'.format(example_yaml)
return
for field in ['name', 'description']:
if field not in metadata:
print 'example.yaml should contain a field {}'.format(
field)
return
resources = []
for file in glob.glob(os.path.join(d, '*')):
with open(file, 'rB') as f:
r = Resource(basename=os.path.basename(file),
contents=f.read())
r.save()
resources.append(r)
e = Example(description=metadata['description'],
name=metadata['name'])
e.save()
e.resources.add(*resources)

View File

@@ -6,13 +6,6 @@ from django.db import models
# Create your models here.
class Program(models.Model):
"""That's a program for ya!"""
# The code
code = models.TextField()
class ToolOutput(models.Model):
"""The result on running a tool on a program"""
@@ -28,9 +21,9 @@ class Resource(models.Model):
"""
# Base name of the resource
name = models.TextField()
basename = models.TextField()
# The contents of the resource
# The contents of the resource if it is a file
contents = models.TextField()
# TODO: if necessary (not sure it is) we can add
@@ -46,3 +39,6 @@ class Example(models.Model):
# A description
description = models.TextField()
# An example is a contains a set of resources
resources = models.ManyToManyField(Resource)

View File

@@ -1,6 +1,6 @@
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from compile_server.app.models import Program
from compile_server.app.models import Resource
class UserSerializer(serializers.HyperlinkedModelSerializer):
@@ -15,17 +15,19 @@ class GroupSerializer(serializers.HyperlinkedModelSerializer):
fields = ('url', 'name')
class ProgramSerializer(serializers.Serializer):
class ResourceSerializer(serializers.Serializer):
class Meta:
model = Program
fields = ('code')
model = Resource
fields = ('basename', 'code')
code = serializers.CharField(style={'base_template': 'textarea.html'})
contents = serializers.CharField(style={'base_template': 'textarea.html'})
basename = serializers.CharField(style={'base_template': 'textarea.html'})
def create(self, validated_data):
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.code = validated_data.get('code', instance.code)
instance.save() # do we actually want to save programs?
instance.contents = validated_data.get('contents', instance.contents)
instance.basename = validated_data.get('basename', instance.basename)
instance.save()
return instance

0
compile_server/app/static/.gitignore vendored Normal file
View File

Submodule compile_server/app/static/ace-builds added at 673da62ea0

View File

@@ -0,0 +1,5 @@
div.editor_container{
height: 30em;
border: 1px solid #ddd;
border-top: 0px;
}

View File

@@ -0,0 +1,121 @@
// Fills a <div> with an editable representation of an example.
// container: the <div> in question
// example_name: the name of the example to load
var unique_id = 0
function fill_editor(container, example_name) {
unique_id++;
container.attr("the_id", unique_id);
// request the examples
$.ajax({
url: "/example/" + example_name,
data: {},
type: "GET",
dataType : "json",
})
.done(function( json ) {
// On success, create editors for each of the resources
// First create the tabs
var ul = $( '<ul class="nav nav-tabs" role="tablist">' )
ul.appendTo(container);
var counter = 0;
json.resources.forEach(function(resource){
counter++;
var the_id = "tab_" + container.attr("the_id") + "-" + counter
var li = $( '<li role="presentation" class="'
+ (counter == 1 ? 'active' : '')
+ '">' ).appendTo(ul);
$('<a href="#' + the_id + '" aria-controls="'
+ the_id + '" '
+ 'id="' + the_id + '-tab"'
+ 'role="tab" data-toggle="tab">'
+ resource.basename + '</a>').appendTo(li)
})
// Then fill the contents of the tabs
var content = $( '<div class="tab-content">' )
content.appendTo(container);
counter = 0;
var editors = []
json.resources.forEach(function(resource){
counter++;
var the_id = "tab_" + container.attr("the_id") + "-" + counter
var div = $('<div role="tabpanel" class="tab-pane'
+ (counter == 1 ? ' active' : '')
+ '" id="' + the_id + '">');
var editordiv = $('<div class="editor_container" id="' + resource.basename + the_id + '_editor">');
editordiv.appendTo(div)
div.appendTo(content);
// ACE editors...
var editor = ace.edit(resource.basename + the_id + '_editor')
editor.session.setMode("ace/mode/ada");
// ... and their contents
editor.insert(resource.contents)
editor.initial_contents = resource.contents
editor.filename = resource.basename
// TODO: place the cursor at 1,1
// Append the editor to the list of editors
editors.push(editor)
})
var toolbar = $('<div class="btn-toolbar">')
toolbar.appendTo(container)
reset_button = $('<button type="button" class="btn btn-secondary">').text("Reset").appendTo(toolbar)
reset_button.editors = editors
reset_button.on('click', function (x){
reset_button.editors.forEach(function (x){
x.setValue(x.initial_contents)
})
})
check_button = $('<button type="button" class="btn btn-primary">').text("Check").appendTo(toolbar)
check_button.editors = editors
check_button.on('click', function (x){
alert(check_button.editors[1].filename)
})
})
.fail(function( xhr, status, errorThrown ) {
//
alert( "could not download the example" );
console.log( "Error: " + errorThrown );
console.log( "Status: " + status );
console.dir( xhr );
})
// Code to run regardless of success or failure;
// commented for now - just so I remember how it's done
// .always(function( xhr, status ) {});
}
// Called when the document is ready
$( document ).ready(function() {
// Iterate on all divs, finding those that have the "example_editor"
// attribute
$( "div" ).each(function(index, element) {
example_name = $( this ).attr("example_editor");
if (example_name) {
fill_editor($( this ), example_name);
}
})
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,30 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Editor</title>
<link rel="stylesheet" href="{% static "common.css" %}" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="{% static "ace-builds/src/ace.js" %}" type="text/javascript" charset="utf-8"></script>
<script src="{% static "jquery-3.2.1.min.js" %}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="{% static "editors.js" %}"></script>
<script>
</script>
</body>
</html>

View File

@@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% block content %}
<div example_editor="{{ example_name }}"></div>
{% endblock%}

View File

@@ -13,9 +13,9 @@ from rest_framework.decorators import api_view
from rest_framework.response import Response
from compile_server.app.serializers import (UserSerializer,
GroupSerializer,
ProgramSerializer)
ResourceSerializer)
from compile_server.app.models import Program
from compile_server.app.models import Resource, Example
class UserViewSet(viewsets.ModelViewSet):
@@ -34,10 +34,10 @@ class GroupViewSet(viewsets.ModelViewSet):
serializer_class = GroupSerializer
class ProgramSet(viewsets.ModelViewSet):
class ResourceSet(viewsets.ModelViewSet):
"""View/Edit"""
queryset = Program.objects.all()
serializer_class = ProgramSerializer
queryset = Resource.objects.all()
serializer_class = ResourceSerializer
@api_view(['POST'])
@@ -51,9 +51,35 @@ def check_program(request):
@api_view(['GET'])
def examples(request):
return Response()
"""Return a list of example names and their description"""
examples = Example.objects.all()
results = []
for e in examples:
results.append({'name': e.name, 'description': e.description})
return Response(results)
@api_view(['GET'])
def example(request, name):
return Response()
# TODO: create an example serializer
matches = Example.objects.filter(name=name)
if not matches:
return Response()
e = matches[0]
resources = []
for r in e.resources.all():
serializer = ResourceSerializer(r)
resources.append(serializer.data)
result = {'name': e.name,
'description': e.description,
'resources': resources}
return Response(result)
def code_page(request, example_name):
# TODO: move to a separate file
context = {'example_name': example_name}
return render(request, 'code_page.html', context)

View File

@@ -23,7 +23,6 @@ from compile_server.app import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
router.register(r'programs', views.ProgramSet)
urlpatterns = [
url(r'^', include(router.urls)),
@@ -34,11 +33,15 @@ urlpatterns = [
# Check one program
url(r'^check_program/', views.check_program),
url(r'^examples/', views.examples),
# Get a list of the examples
url(r'^examples/', views.examples),
# Get the details on one example
url(r'^example/([^/])+', views.example),
url(r'^example/([^\/]+)$', views.example),
# HTML urls
# Get the code viewer on one example
url(r'^code_page/([^\/]+)$', views.code_page),
]

View File

@@ -1,5 +1,37 @@
Notes just for myself
# Command to use curl to check a program
# Add -u <login>:<password> when using authentication
# launch a shell
./manage.py shell
########
# Todo #
########
Frontend
* fix selection after clicking the "reset" button
* nicer display when an example was not found
* grab the bits that are currently served by CDNs
Backend
* cleanup the status of imported/invisible files (exclude yaml, project)
* implement the "check" button
* implement a page which lists all the examples
Production
* integrate Yannick's examples
* run a server at AdaCore
#################
# Curl commands #
#################
Add -u <login>:<password> when using authentication
# check a program
curl -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/check_program/ --data "{\"program\": \"hello\"}" --header "Content-Type:application/json"
# get the examples
curl -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/examples/ --header "Content-Type:application/json"

View File

@@ -1,9 +1,12 @@
project langserv is
for Languages use ("Python", "Text");
for Languages use ("Python", "Text", "HTML", "javascript", "css");
package Naming is
for Implementation_Suffix ("Text") use ".txt";
for Implementation_Suffix ("HTML") use ".txt";
for Implementation_Suffix ("javascript") use ".js";
for Implementation_Suffix ("css") use ".css";
end Naming;
for Source_Dirs use (-- The django base
@@ -17,7 +20,13 @@ project langserv is
"compile_server/app/management/commands",
-- Some design docs
"design"
"design",
-- The static resources
"compile_server/app/static/",
-- The HTML templates
"compile_server/app/templates/**"
);

View File

@@ -0,0 +1 @@
asdsad

View File

@@ -0,0 +1 @@
asdsadadadsadad

View File

@@ -0,0 +1,2 @@
name: test example
description: test example description