edit recipes
This commit is contained in:
parent
a2a28007f3
commit
1a15b3caf1
|
@ -53,7 +53,5 @@ class MockPIN(Actuator):
|
||||||
# print('FAKE DISABLE')
|
# print('FAKE DISABLE')
|
||||||
|
|
||||||
actuators = {
|
actuators = {
|
||||||
'heater': GPIOPin(
|
'heater': (GPIOPin if rpi else MockPIN)(pin=22, initial=GPIO.HIGH, onstate=GPIO.LOW, offstate=GPIO.HIGH),
|
||||||
pin=22, initial=GPIO.HIGH, onstate=GPIO.LOW, offstate=GPIO.HIGH) if (
|
|
||||||
rpi) else MockPIN(pin=22, initial=False, onstate=True, offstate=False),
|
|
||||||
}
|
}
|
||||||
|
|
19
app.py
19
app.py
|
@ -51,9 +51,14 @@ def make_state():
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
@app.route('/recipe-editor')
|
@app.route('/edit-recipe/<id>')
|
||||||
def recipe_editor():
|
def recipe_editor(id):
|
||||||
return render_template('recipe-editor.html')
|
return render_template('recipe-editor.html',
|
||||||
|
recipe=statemachine.recipeById(id))
|
||||||
|
|
||||||
|
@app.route('/edit-recipe/')
|
||||||
|
def recipes_list():
|
||||||
|
return render_template('recipes-list.html', recipes=statemachine.recipes)
|
||||||
|
|
||||||
@app.route('/status')
|
@app.route('/status')
|
||||||
def status():
|
def status():
|
||||||
|
@ -88,6 +93,14 @@ def handle_manual_response(response):
|
||||||
def load_recipe_idx(idx):
|
def load_recipe_idx(idx):
|
||||||
statemachine.loadByIdx(idx)
|
statemachine.loadByIdx(idx)
|
||||||
|
|
||||||
|
@socketio.on('update recipe phase')
|
||||||
|
def update_recipe_phase(recipeid, phaseid, content):
|
||||||
|
recipe = statemachine.recipeById(recipeid)
|
||||||
|
recipe.phases[phaseid] = phasectrl.load_phase(content)
|
||||||
|
statemachine.recipes[recipeid] = recipe
|
||||||
|
phasectrl.store_recipes("recipes.json",
|
||||||
|
[r.getState() for r in statemachine.recipes])
|
||||||
|
|
||||||
@socketio.on('stop recipe')
|
@socketio.on('stop recipe')
|
||||||
def stop_recipe():
|
def stop_recipe():
|
||||||
statemachine.stop()
|
statemachine.stop()
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
var state = {};
|
var state = {};
|
||||||
var localstate = {
|
var localstate = {
|
||||||
manual: {}, recipe: 0, page: ['General', 'Dashboard'],
|
manual: {}, recipe: 0, page: ['General', 'Dashboard'],
|
||||||
maxpoints: 1000
|
maxpoints: 1000,
|
||||||
|
editing: {}
|
||||||
};
|
};
|
||||||
var menu_items = [
|
var menu_items = [
|
||||||
{name: "General",
|
{name: "General",
|
||||||
|
@ -12,7 +13,7 @@ var menu_items = [
|
||||||
},
|
},
|
||||||
{name: "Recipes",
|
{name: "Recipes",
|
||||||
subitems:[
|
subitems:[
|
||||||
{name: "Recipes Editor", link: 'recipe-editor'},
|
{name: "Recipes Editor", link: 'edit-recipe'},
|
||||||
{name: "New Recipe"}
|
{name: "New Recipe"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -54,6 +55,8 @@ function applyState(newstate) {
|
||||||
current_recipe(state.recipe);
|
current_recipe(state.recipe);
|
||||||
render_load_recipe(state);
|
render_load_recipe(state);
|
||||||
render_plot();
|
render_plot();
|
||||||
|
localstate.editing.recipe = state.recipes.findIndex(
|
||||||
|
function (el) {return el.name == localstate.editing.recipe_name;});
|
||||||
}
|
}
|
||||||
|
|
||||||
var enable_draw = true;
|
var enable_draw = true;
|
||||||
|
@ -124,7 +127,7 @@ function render_sensors(sensordata) {
|
||||||
|
|
||||||
function navigate(path, link) {
|
function navigate(path, link) {
|
||||||
if (link !== undefined) {
|
if (link !== undefined) {
|
||||||
window.location.href = "./" + link
|
window.location.href = "/" + link
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
localstate.page = path;
|
localstate.page = path;
|
||||||
|
@ -160,6 +163,93 @@ function load_recipe() {
|
||||||
socket.emit('load recipe idx', localstate.recipe);
|
socket.emit('load recipe idx', localstate.recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function edit_recipe(recipeid) {
|
||||||
|
let recipe = recipeid === undefined ? localstate.recipe : recipeid;
|
||||||
|
window.location.href = '/edit-recipe/'+recipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
function currently_editing_recipe(name) {
|
||||||
|
localstate.editing.recipe_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_field(fieldname, newvalue) {
|
||||||
|
state.recipes[localstate.editing.recipe].phases[localstate.editing.phase][fieldname] = newvalue;
|
||||||
|
console.log(state.recipes[localstate.editing.recipe].phases[localstate.editing.phase]);
|
||||||
|
console.log(fieldname, newvalue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_phase() {
|
||||||
|
let edited_phase = state.recipes[localstate.editing.recipe].phases[
|
||||||
|
localstate.editing.phase];
|
||||||
|
console.log(edited_phase);
|
||||||
|
socket.emit('update recipe phase',
|
||||||
|
localstate.editing.recipe, localstate.editing.phase, edited_phase)
|
||||||
|
}
|
||||||
|
|
||||||
|
function edit_phase(phaseid) {
|
||||||
|
console.log(localstate);
|
||||||
|
let html = document.getElementById("phase-editor");
|
||||||
|
if (html === null) return;
|
||||||
|
localstate.editing.phase = phaseid;
|
||||||
|
data = state.recipes[localstate.editing.recipe].phases[phaseid];
|
||||||
|
console.log(data.text);
|
||||||
|
let template = `<div class="field">
|
||||||
|
<label class="label">Phase Name</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input"
|
||||||
|
oninput="update_field('name', this.value)"
|
||||||
|
type="text" placeholder="Phase Name" value="{{name}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Phase Description</label>
|
||||||
|
<div class="control">
|
||||||
|
<textarea class="textarea" placeholder="Phase Description"
|
||||||
|
oninput="update_field('text', this.value)">{{text}}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">On Load</label>
|
||||||
|
<div class="control">
|
||||||
|
<textarea
|
||||||
|
oninput="update_field('onload', this.value)"
|
||||||
|
class="textarea" placeholder="#t">{{onload}}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Next Condition</label>
|
||||||
|
<div class="control">
|
||||||
|
<textarea oninput="update_field('nextcond', this.value)"
|
||||||
|
class="textarea" placeholder="#t">{{nextcond}}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">On Exit</label>
|
||||||
|
<div class="control">
|
||||||
|
<textarea oninput="update_field('onexit', this.value)"
|
||||||
|
class="textarea" placeholder="#t">{{onexit}}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-link"
|
||||||
|
onclick="save_phase();">Save</button>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-link is-light"
|
||||||
|
onclick="stop_editing_phase();">Cancel</button>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-danger"
|
||||||
|
onclick="delete_phase();">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
html.innerHTML = Mustache.render(template, data);
|
||||||
|
}
|
||||||
|
|
||||||
function stop_recipe() {
|
function stop_recipe() {
|
||||||
socket.emit('stop recipe');
|
socket.emit('stop recipe');
|
||||||
}
|
}
|
||||||
|
@ -193,6 +283,7 @@ function render_load_recipe(data) {
|
||||||
</div>
|
</div>
|
||||||
{{#selected_recipe}}
|
{{#selected_recipe}}
|
||||||
{{description}}
|
{{description}}
|
||||||
|
<br/><br/>
|
||||||
Controllers:<br/>
|
Controllers:<br/>
|
||||||
<ul>
|
<ul>
|
||||||
{{#controllers}}
|
{{#controllers}}
|
||||||
|
@ -203,7 +294,6 @@ Controllers:<br/>
|
||||||
<br><br>
|
<br><br>
|
||||||
<div class="content"><h4>{{name}}</h4></div>
|
<div class="content"><h4>{{name}}</h4></div>
|
||||||
{{text}}<br>
|
{{text}}<br>
|
||||||
Ranges: FIXME<br>
|
|
||||||
On Load: {{onload}}<br>
|
On Load: {{onload}}<br>
|
||||||
Exit When: {{nextcond}}<br>
|
Exit When: {{nextcond}}<br>
|
||||||
On Exit: {{onexit}}<br>
|
On Exit: {{onexit}}<br>
|
||||||
|
@ -213,7 +303,8 @@ On Exit: {{onexit}}<br>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
<a onclick="load_recipe()"
|
<a onclick="load_recipe()"
|
||||||
class="card-footer-item is-primary">Load</a>
|
class="card-footer-item is-primary">Load</a>
|
||||||
<a class="card-footer-item">Edit</a>
|
<a onclick="edit_recipe()"
|
||||||
|
class="card-footer-item">Edit</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>`
|
</div>`
|
||||||
html.innerHTML = Mustache.render(template, data);
|
html.innerHTML = Mustache.render(template, data);
|
||||||
|
|
32
phasectrl.py
32
phasectrl.py
|
@ -20,24 +20,30 @@ def safe_eval(data, env):
|
||||||
chat.send_sync(err)
|
chat.send_sync(err)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def load_recipe(json):
|
|
||||||
def load_phase(json):
|
def load_phase(json):
|
||||||
def fallback(v):
|
def fallback(v):
|
||||||
return json.get(v, '#t')
|
return json.get(v, '#t')
|
||||||
return Phase(
|
return Phase(
|
||||||
json['name'], json['description'],
|
json['name'], json['text'],
|
||||||
onexit=fallback('on_exit'),
|
onexit=fallback('on_exit'),
|
||||||
onload=fallback('on_load'),
|
onload=fallback('on_load'),
|
||||||
nextcond=fallback('exit_condition'))
|
nextcond=fallback('exit_condition'))
|
||||||
|
|
||||||
|
def load_recipe(json):
|
||||||
return Recipe(
|
return Recipe(
|
||||||
json['name'], json['variant'], json['description'],
|
json['name'], json['description'],
|
||||||
json.get('controllers', ()),
|
json.get('controllers', ()),
|
||||||
tuple(load_phase(p) for p in json['phases']))
|
[load_phase(p) for p in json['phases']])
|
||||||
|
|
||||||
def load_recipes(file):
|
def load_recipes(file):
|
||||||
with open(file, "r") as f:
|
with open(file, "r") as f:
|
||||||
recipes = json.loads(f.read())
|
recipes = json.loads(f.read())
|
||||||
return tuple(load_recipe(recipe) for recipe in recipes)
|
return [load_recipe(recipe) for recipe in recipes]
|
||||||
|
|
||||||
|
def store_recipes(file, recipes):
|
||||||
|
print(file, recipes)
|
||||||
|
with open(file, "w") as f:
|
||||||
|
f.write(json.dumps(recipes, indent=2))
|
||||||
|
|
||||||
def manual_intervention_wrapper(state):
|
def manual_intervention_wrapper(state):
|
||||||
def wrapper(args):
|
def wrapper(args):
|
||||||
|
@ -101,8 +107,18 @@ class State():
|
||||||
self.envdata['sensors'] = {}
|
self.envdata['sensors'] = {}
|
||||||
self.loadphase(self.phase)
|
self.loadphase(self.phase)
|
||||||
|
|
||||||
|
def recipeById(self, id):
|
||||||
|
try:
|
||||||
|
id = int(id)
|
||||||
|
except:
|
||||||
|
id = -1
|
||||||
|
print(self.recipes)
|
||||||
|
if id > -1 and id < len(self.recipes):
|
||||||
|
return self.recipes[id]
|
||||||
|
return None
|
||||||
|
|
||||||
def loadByIdx(self, recipe):
|
def loadByIdx(self, recipe):
|
||||||
self.recipe = self.recipes[recipe]
|
self.recipe = self.recipeById(recipe)
|
||||||
self.postload()
|
self.postload()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
@ -192,9 +208,9 @@ class State():
|
||||||
'(', self.envdata['controllers'][controller], ')')
|
'(', self.envdata['controllers'][controller], ')')
|
||||||
|
|
||||||
class Recipe():
|
class Recipe():
|
||||||
def __init__(self, name, variant='default', description='',
|
def __init__(self, name, description='',
|
||||||
controllers=(),
|
controllers=(),
|
||||||
phases=()):
|
phases=[]):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.controllers = controllers
|
self.controllers = controllers
|
||||||
self.description = description
|
self.description = description
|
||||||
|
|
64
recipes.json
64
recipes.json
|
@ -1,90 +1,92 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "Tepache",
|
"name": "Tepache",
|
||||||
"variant": "default",
|
"description": "Tiene il tepache a ~23 gradi",
|
||||||
"controllers": [
|
"controllers": [
|
||||||
"(make-duty-controller \"heater\" 0.20 120)"
|
"(make-duty-controller \"heater\" 0.20 120)"
|
||||||
],
|
],
|
||||||
"description": "Tiene il tepache a ~23 gradi",
|
|
||||||
"phases": [
|
"phases": [
|
||||||
{
|
{
|
||||||
"name": "Hold",
|
"name": "Hold",
|
||||||
"description": "Keep 23°C",
|
"text": "Keep 23\u00b0C",
|
||||||
"on_load": "(begin (set-controller \"heater\" \"T_food\")\n(set-target \"T_food\" 23.0))",
|
"nextcond": "#t",
|
||||||
"exit_condition": "(> (time-in-this-phase) (hours 10))"
|
"current": false,
|
||||||
|
"onload": "#t",
|
||||||
|
"onexit": "#t"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Yogurt",
|
"name": "Yogurt",
|
||||||
"variant": "default",
|
"description": "Pastorizza lo yogurt a 82 gradi, lascia raffreddare per fare l'inoculo, mantiene a 42 gradi fino a che \u00e8 pronto.",
|
||||||
"controllers": [
|
"controllers": [
|
||||||
"(make-duty-controller \"heater\" 0.20 120)"
|
"(make-duty-controller \"heater\" 0.20 120)"
|
||||||
],
|
],
|
||||||
"description": "Pastorizza lo yogurt a 82 gradi, lascia raffreddare per fare l'inoculo, mantiene a 42 gradi fino a che è pronto.",
|
|
||||||
"phases": [
|
"phases": [
|
||||||
{
|
{
|
||||||
"name": "Preheat",
|
"name": "Preheat",
|
||||||
"description": "Preheat yogurt to 82°",
|
"text": "Preheat yogurt to 82\u00b0",
|
||||||
"on_load": "(begin (set-controller \"heater\" \"T_food\")\n(set-target \"T_food\" 82.0))",
|
"nextcond": "#t",
|
||||||
"exit_condition": "(and (>= (get-sensor \"T_food\") 81.5)\n(> (time-in-this-phase) (minutes 15)))"
|
"current": false,
|
||||||
|
"onload": "#t",
|
||||||
|
"onexit": "#t"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Cool",
|
"name": "Cool",
|
||||||
"description": "Cool to 46°",
|
"text": "Cool to 46\u00b0",
|
||||||
"on_load": "(set-target \"T_food\" 46.0)",
|
"nextcond": "#t",
|
||||||
"exit_condition": "(and (< (get-sensor \"T_food\") 46.0)\n(> (get-sensor \"T_food\") 42.0))"
|
"current": false,
|
||||||
|
"onload": "#t",
|
||||||
|
"onexit": "#t"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Inoculate",
|
"name": "Inoculate (Backslopping)",
|
||||||
"description": "Add 50ml Yogurt",
|
"text": "Add 50ml of an old Yogurt batch",
|
||||||
"on_load": "(notify \"Inoculate 50g of yogurt NOW!\")",
|
"nextcond": "#t",
|
||||||
"exit_condition": "(manual-intervention \"Inocula 50g di yogurt\" '((\"Fatto\" #t)))",
|
"current": false,
|
||||||
"on_exit": "(notify (concat \"Inoculated at \" (now)))"
|
"onload": "#t",
|
||||||
|
"onexit": "#t"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Hold",
|
"name": "Hold",
|
||||||
"description": "Keep at 42° for 3h",
|
"text": "Keep at 42\u00b0 for 3h",
|
||||||
"on_load": "(set-target \"T_food\" 41.0)",
|
"nextcond": "#t",
|
||||||
"exit_condition": "(> (time-in-this-phase) (hours 3))",
|
"current": false,
|
||||||
"on_exit": "(notify \"You can remove the yogurt from the heater\")"
|
"onload": "#t",
|
||||||
|
"onexit": "#t"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Nattō",
|
"name": "Natt\u014d",
|
||||||
"variant": "default",
|
"description": "FIXME",
|
||||||
"controllers": [
|
"controllers": [
|
||||||
"(make-duty-controller \"heater\" 0.20 120)"
|
"(make-duty-controller \"heater\" 0.20 120)"
|
||||||
],
|
],
|
||||||
"description": "FIXME",
|
|
||||||
"phases": []
|
"phases": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Tempeh",
|
"name": "Tempeh",
|
||||||
"variant": "default",
|
"description": "FIXME",
|
||||||
"controllers": [
|
"controllers": [
|
||||||
"(make-duty-controller \"heater\" 0.20 120)"
|
"(make-duty-controller \"heater\" 0.20 120)"
|
||||||
],
|
],
|
||||||
"description": "FIXME",
|
|
||||||
"phases": []
|
"phases": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Koji Rice",
|
"name": "Koji Rice",
|
||||||
"variant": "default",
|
"description": "FIXME",
|
||||||
"controllers": [
|
"controllers": [
|
||||||
"(make-duty-controller \"heater\" 0.20 120)"
|
"(make-duty-controller \"heater\" 0.20 120)"
|
||||||
],
|
],
|
||||||
"description": "FIXME",
|
|
||||||
"phases": []
|
"phases": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Gorgonzola",
|
"name": "Gorgonzola",
|
||||||
"variant": "default",
|
"description": "FIXME",
|
||||||
"controllers": [
|
"controllers": [
|
||||||
"(make-duty-controller \"heater\" 0.20 120)"
|
"(make-duty-controller \"heater\" 0.20 120)"
|
||||||
],
|
],
|
||||||
"description": "FIXME",
|
|
||||||
"phases": []
|
"phases": []
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -5,86 +5,50 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- https://codepen.io/PJCHENder/pen/PKBVRO/ -->
|
<!-- https://codepen.io/PJCHENder/pen/PKBVRO/ -->
|
||||||
<ul id="items-list" class="moveable">
|
<!-- <ul id="items-list" class="moveable"> -->
|
||||||
<li>One</li>
|
<!-- <li>One</li> -->
|
||||||
<li>Two</li>
|
<!-- <li>Two</li> -->
|
||||||
<li>Three</li>
|
<!-- <li>Three</li> -->
|
||||||
<li>Four</li>
|
<!-- <li>Four</li> -->
|
||||||
</ul>
|
<!-- </ul> -->
|
||||||
|
<script>currently_editing_recipe('{{recipe.name}}');</script>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Recipe Name</label>
|
<label class="label">Recipe Name</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="text" placeholder="Text input">
|
<input class="input" type="text" placeholder="Text input"
|
||||||
|
value="{{recipe.name}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Description</label>
|
<label class="label">Description</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<textarea class="textarea" placeholder="Recipe Description"></textarea>
|
<textarea class="textarea" placeholder="Recipe Description">
|
||||||
|
{{recipe.description}}
|
||||||
|
</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Phase list:
|
Phase list:
|
||||||
|
{% for phase in recipe.phases %}
|
||||||
<div class="tile is-ancestor">
|
<div class="tile is-ancestor">
|
||||||
<div class="tile is-vertical">
|
<div class="tile is-vertical">
|
||||||
<div class="tile">
|
<div class="tile">
|
||||||
<div class="tile is-parent is-vertical">
|
<div class="tile is-parent is-vertical">
|
||||||
<article class="tile is-child notification is-primary">
|
<article
|
||||||
|
onclick="edit_phase({{loop.index0}});"
|
||||||
|
class="tile is-child notification is-primary">
|
||||||
<p class="title"></p>
|
<p class="title"></p>
|
||||||
<p class="subtitle">Top tile</p>
|
<p class="subtitle">{{ phase.name }}</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-half">
|
<div class="column is-half" id="phase-editor">
|
||||||
<div class="field">
|
|
||||||
<label class="label">Phase Name</label>
|
|
||||||
<div class="control">
|
|
||||||
<input class="input" type="text" placeholder="Phase Name">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">Phase Description</label>
|
|
||||||
<div class="control">
|
|
||||||
<textarea class="textarea" placeholder="Phase Description"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">On Load</label>
|
|
||||||
<div class="control">
|
|
||||||
<textarea class="textarea" placeholder="#t"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">Next Condition</label>
|
|
||||||
<div class="control">
|
|
||||||
<textarea class="textarea" placeholder="(> (time-in-this-phase) (minutes 1))"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">On Exit</label>
|
|
||||||
<div class="control">
|
|
||||||
<textarea class="textarea" placeholder="#t"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field is-grouped">
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-link">Submit</button>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-link is-light">Cancel</button>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-danger">Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>{% block title %}Hakkoso{% endblock %}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-half">
|
||||||
|
<button class="button is-link">New Recipe</button>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
{% for recipe in recipes %}
|
||||||
|
<div class="tile is-ancestor">
|
||||||
|
<div class="tile is-vertical">
|
||||||
|
<div class="tile">
|
||||||
|
<div class="tile is-parent is-vertical">
|
||||||
|
<article
|
||||||
|
onclick="edit_recipe({{loop.index0}});"
|
||||||
|
class="tile is-child notification is-primary">
|
||||||
|
<p class="title">{{ recipe.name }}</p>
|
||||||
|
<p class="subtitle">
|
||||||
|
{% for phase in recipe.phases %}
|
||||||
|
{{ phase.name }}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="column is-half" id="phase-editor">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue