You've already forked code_examples_server
mirror of
https://github.com/AdaCore/code_examples_server.git
synced 2026-02-12 12:45:18 -08:00
Merge pull request #1 from setton/master
Initial implementation of the frontend
This commit is contained in:
39
README.md
39
README.md
@@ -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
2
REQUIREMENTS.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
django
|
||||
djangorestframework
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
0
compile_server/app/static/.gitignore
vendored
Normal file
1
compile_server/app/static/ace-builds
Submodule
1
compile_server/app/static/ace-builds
Submodule
Submodule compile_server/app/static/ace-builds added at 673da62ea0
5
compile_server/app/static/common.css
Normal file
5
compile_server/app/static/common.css
Normal file
@@ -0,0 +1,5 @@
|
||||
div.editor_container{
|
||||
height: 30em;
|
||||
border: 1px solid #ddd;
|
||||
border-top: 0px;
|
||||
}
|
||||
121
compile_server/app/static/editors.js
Normal file
121
compile_server/app/static/editors.js
Normal 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);
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
4
compile_server/app/static/jquery-3.2.1.min.js
vendored
Normal file
4
compile_server/app/static/jquery-3.2.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
30
compile_server/app/templates/base.html
Normal file
30
compile_server/app/templates/base.html
Normal 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>
|
||||
6
compile_server/app/templates/code_page.html
Normal file
6
compile_server/app/templates/code_page.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
|
||||
<div example_editor="{{ example_name }}"></div>
|
||||
|
||||
{% endblock%}
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
|
||||
13
langserv.gpr
13
langserv.gpr
@@ -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/**"
|
||||
);
|
||||
|
||||
|
||||
|
||||
1
resources/example/a/bla.txt
Normal file
1
resources/example/a/bla.txt
Normal file
@@ -0,0 +1 @@
|
||||
asdsad
|
||||
1
resources/example/a/bla2.txt
Normal file
1
resources/example/a/bla2.txt
Normal file
@@ -0,0 +1 @@
|
||||
asdsadadadsadad
|
||||
2
resources/example/a/example.yaml
Normal file
2
resources/example/a/example.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
name: test example
|
||||
description: test example description
|
||||
Reference in New Issue
Block a user