diff --git a/rowers/models.py b/rowers/models.py
index c9bfcbd4..5823717e 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -2391,6 +2391,111 @@ regularsessiontypechoices = (
)
# model for Planned Session (Workout, Challenge, Test)
+class PlannedSessionStep(models.Model):
+ intensitytypes = (
+ ("Active", "Active"),
+ ("Rest", "Rest"),
+ ("Warmup", "Warmup"),
+ ("Cooldown", "Cooldown")
+ )
+
+ durationtypes = (
+ ("Distance", "Distance"),
+ ("Time", "Time"),
+ ('RepeatUntilStepsCmplt','Repeat previous blocks n times')
+ )
+
+ targettypes = (
+ ("Speed", "Speed"),
+ ("HeartRate", "HeartRate"),
+ ("Cadence", "Cadence"),
+ ("Power", "Power")
+ )
+
+ manager = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
+ name = models.TextField(default='',max_length=200, blank=True, null=True)
+ type = models.TextField(default='',max_length=200, blank=True, null=True)
+ durationvalue = models.FloatField(default=0, verbose_name="Duration Value")
+ durationtype = models.TextField(default='Time',max_length=200,
+ choices=durationtypes,
+ verbose_name='Duration Type')
+ targetvalue = models.IntegerField(default=0, verbose_name="Target Value")
+ targettype = models.TextField(default='',max_length=200, blank=True, null=True,
+ choices=targettypes, verbose_name="Target Type")
+ targetvaluelow = models.IntegerField(default=0,
+ verbose_name="Target Value Low")
+ targetvaluehigh = models.IntegerField(default=0,
+ verbose_name="Target Value High")
+ intensity = models.TextField(default='',max_length=200, blank=True, null=True,
+ choices=intensitytypes,
+ verbose_name = "Intensity")
+ description = models.TextField(default='',max_length=200, blank=True, null=True)
+ color = models.TextField(default='#ddd',max_length=200)
+
+ def save(self, *args, **kwargs):
+ if self.intensity == "Warmup":
+ self.color = "#ffcccb"
+ elif self.intensity == "Cooldown":
+ self.color = '#90ee90'
+ elif self.intensity == "Rest":
+ self.color = 'add8e6'
+ if self.durationtype == 'RepeatUntilStepsCmplt':
+ self.color = 'ffffa7'
+
+ self.durationvalue = int(self.durationvalue)
+
+ super(PlannedSessionStep, self).save(*args, **kwargs)
+
+ def asdict(self):
+ d = {
+ 'wkt_step_name': self.name,
+ 'durationType': self.durationtype,
+ 'durationValue': self.durationvalue,
+ 'targetType': self.targettype,
+ 'targetValue': self.targetvalue,
+ 'targetValueLow': self.targetvaluelow,
+ 'targetValueHigh': self.targetvaluehigh,
+ 'description': self.description,
+ 'stepId': self.pk,
+ 'intensity': self.intensity,
+ }
+
+ return d
+
+class StepEditorForm(ModelForm):
+ class Meta:
+ model = PlannedSessionStep
+ fields = [
+ 'name',
+ #'type',
+ 'durationtype',
+ 'durationvalue',
+ 'targettype',
+ 'targetvalue',
+ 'targetvaluelow',
+ 'targetvaluehigh',
+ 'intensity',
+ 'description',
+ ]
+
+ widgets = {
+ 'name': forms.Textarea(attrs={'rows':1, 'cols':50}),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(StepEditorForm, self).__init__(*args, **kwargs)
+ if self.instance.durationtype == 'Time':
+ self.initial['durationvalue'] = self.instance.durationvalue / 60000
+ elif self.instance.durationtype == 'Distance':
+ self.initial['durationvalue'] = self.instance.durationvalue / 100
+
+ def save(self, *args, **kwargs):
+ # conversions
+ if self.instance.durationtype == 'Time':
+ self.instance.durationvalue *= 60000
+ elif self.instance.durationtype == 'Distance':
+ self.instance.durationvalue *= 100
+ return super(StepEditorForm, self).save(*args, **kwargs)
class PlannedSession(models.Model):
diff --git a/rowers/templates/plannedsessiontemplatecreate.html b/rowers/templates/plannedsessiontemplatecreate.html
index fbda21b5..c1d89a73 100644
--- a/rowers/templates/plannedsessiontemplatecreate.html
+++ b/rowers/templates/plannedsessiontemplatecreate.html
@@ -34,6 +34,8 @@
+
+
{% endblock %}
{% block sidebar %}
diff --git a/rowers/templates/plannedsessiontemplateedit.html b/rowers/templates/plannedsessiontemplateedit.html
index d3c15a01..ea18075b 100644
--- a/rowers/templates/plannedsessiontemplateedit.html
+++ b/rowers/templates/plannedsessiontemplateedit.html
@@ -35,6 +35,17 @@
+
+ Steps
+ {% if steps %}
+ {{ steps|safe }}
+ {% else %}
+ No Steps defined
+ {% endif %}
+
+ Edit Steps (experimental)
+
+
{% endblock %}
diff --git a/rowers/templates/stepedit.html b/rowers/templates/stepedit.html
new file mode 100644
index 00000000..c53575dd
--- /dev/null
+++ b/rowers/templates/stepedit.html
@@ -0,0 +1,97 @@
+{% extends "newbase.html" %}
+{% load static %}
+{% load rowerfilters %}
+
+{% block title %}Rowsandall Training Plans{% endblock %}
+
+
+{% block main %}
+Edit {{ step.name }}
+
+ -
+
+ WARNING: This is experimental functionality which may not behave as you
+ expect. Does not work on smartphones.
+
+
+-
+
Step Description
+ {{ stepdescription }}
+
+-
+
Add new step
+
+
+-
+
Explanation
+
+
+ | Parameter |
+ Regular Step |
+ Repeat Step |
+
+
+ | Duration Type | Time or Distance | Repeat |
+
+
+ | Duration Value | Minutes / Meters | Block number to start repeat from |
+
+
+ | Target Type | Set a target to hold | --- |
+
+
+ | Target Value |
+
+ Power: Zone number (1-10), % of FTP (10-1000)
+ Speed: 1000x target speed in m/s
+ Heart Rate: Zone number (1-10), % of max (10-100);
+ Cadence: Strokes per Minute
+ |
+
+ Number of repetitions
+ |
+
+
+ | Target Value Low |
+
+ Power: % of FTP (10-1000)
+ Speed: 1000x target speed in m/s
+ Heart Rate: % of max (10-100);
+ Cadence: Strokes per Minute
+ |
+
+ |
+
+
+ | Target Value High |
+
+ Power: % of FTP (10-1000)
+ Speed: 1000x target speed in m/s
+ Heart Rate: % of max (10-100);
+ Cadence: Strokes per Minute
+ |
+
+ |
+
+
+ | Intensity | Warming Up, Active, Rest | Set to Active |
+
+
+ | Description | Any other text | |
+
+
+
+
+{% endblock %}
+
+
+{% block sidebar %}
+{% include 'menu_plan.html' %}
+{% endblock %}
diff --git a/rowers/templates/stepeditor.html b/rowers/templates/stepeditor.html
new file mode 100644
index 00000000..0d82a71b
--- /dev/null
+++ b/rowers/templates/stepeditor.html
@@ -0,0 +1,343 @@
+{% extends "newbase.html" %}
+{% load static %}
+{% load rowerfilters %}
+
+{% block title %}Rowsandall Training Plans{% endblock %}
+
+
+{% block main %}
+Plan Training Steps
+
+ WARNING: This is experimental functionality which may not behave as you
+ expect. Does not work on smartphones.
+
+
+ Drag from Library to Training to add a step to the end.
+ Drag on top of a training step to insert after it.
+ Drag out of Training to remove a step.
+
+
+
+Training Steps for {{ ps.name }}
+
+
+
+{% for step in currentsteps %}
+
+
+ ({{ forloop.counter|add:-1 }})
+
+
+ {{ step.name }}
+ {% if step.durationtype == "RepeatUntilStepsCmplt" %}
+ - repeat {{ step.targetvalue }}x from block {{ step.durationvalue|floatformat }}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+{% endfor %}
+
+
+
+ Step Library
+ {% for step in steps %}
+
+
+
+
+ {{ step.name }}
+ {% if step.durationtype == "RepeatUntilStepsCmplt" %}
+ - repeat {{ step.targetvalue }}x from block {{ step.durationvalue|floatformat }}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+
+-
+
Add new step
+
+
+-
+
Step Information
+
+
+
+{% endblock %}
+
+{% block scripts %}
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_plan.html' %}
+{% endblock %}
diff --git a/rowers/templates/trainingplan_create.html b/rowers/templates/trainingplan_create.html
index 7f683aa4..2205f102 100644
--- a/rowers/templates/trainingplan_create.html
+++ b/rowers/templates/trainingplan_create.html
@@ -123,15 +123,20 @@
{% if plan.status %} active {% else %} inactive {% endif %} |
{% if request.user.rower == plan.manager %}
- Edit
+
{% endif %}
|
- Plan |
+ |
{% if request.user.rower == plan.manager %}
- Delete
+
{% endif %}
|
+
+
+
+
+ |
{% endfor %}
diff --git a/rowers/urls.py b/rowers/urls.py
index 4251c30e..49cfa865 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -875,6 +875,18 @@ urlpatterns = [
views.rower_create_trainingplan, name='rower_create_trainingplan'),
re_path(r'^plans/$', views.rower_select_instantplan,
name='rower_select_instantplan'),
+ re_path(r'^plans/step/(?P\d+)/edit/$',
+ views.stepedit, name='stepedit'),
+ re_path(r'^plans/step/(?P\d+)/edit/(?P\d+)/$',
+ views.stepedit, name='stepedit'),
+ re_path(r'^plans/step/(?P\d+)/delete/$',
+ views.stepdelete, name='stepdelete'),
+ re_path(r'^plans/stepeditor/$',
+ views.stepeditor, name='stepeditor'),
+ re_path(r'^plans/stepeditor/(?P\d+)/$',
+ views.stepeditor, name='stepeditor'),
+ re_path(r'^plans/stepadder/(?P\d+)/$',
+ views.stepadder, name='stepadder'),
re_path(r'^plans/(?P[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})/$',
views.rower_view_instantplan, name='rower_view_instantplan'),
re_path(r'^buyplan/(?P\d+)/$', views.buy_trainingplan_view,
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index ade55c80..0a6221d4 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -6,6 +6,8 @@ from rowers.views.statements import *
import rowers.garmin_stuff as gs
from rowers import credits
+from json.decoder import JSONDecodeError
+from rowers.utils import step_to_string
@login_required
@@ -1403,9 +1405,24 @@ def save_plan_yaml(request, userid=0):
for ps in sps:
if ps.preferreddate == dd:
sessionsport = mytypes.fitmapping[ps.sessionsport].capitalize()
- steps = ps.steps
- steps['filename'] = ""
- workouts.append(steps)
+ if ps.steps:
+ steps = ps.steps
+ steps['filename'] = ""
+ workouts.append(steps)
+ else:
+ if ps.sessionmode == 'distance':
+ ps.interval_string = '{d}m'.format(d=ps.sessionvalue)
+ elif ps.sessionmode == 'time':
+ ps.interval_string = '{d}min'.format(d=ps.sessionvalue)
+ ps.fitfile = ''
+ ps.steps = None
+ ps.save()
+ ps_reload = PlannedSession.objects.get(id=ps.id)
+ steps = ps_reload.steps
+ steps['filename'] = ""
+ steps['workoutName'] = ps.name
+ print(steps)
+ workouts.append(steps)
trainingdays.append({'order': i+1, 'workouts': workouts})
@@ -2003,6 +2020,12 @@ def plannedsession_templateedit_view(request, id=0):
sessiontemplates = sessiontemplates | sessiontemplates2
+ steps = ''
+ if ps.steps: # pragma: no cover
+ d = ps.steps
+
+ steps = ps_dict_get_description_html(d, short=False)
+
return render(request, 'plannedsessiontemplateedit.html',
{
'teams': get_my_teams(request.user),
@@ -2013,6 +2036,7 @@ def plannedsession_templateedit_view(request, id=0):
'thesession': ps,
'sessiontemplates': sessiontemplates,
'rower': r,
+ 'steps': steps,
})
@@ -2959,6 +2983,286 @@ def rower_create_trainingplan(request, id=0):
'old_targets': old_targets,
})
+@user_passes_test(can_plan, login_url="/rowers/paidplans",
+ message="This functionality requires a Coach or Self-Coach plan",
+ redirect_field_name=None)
+def stepadder(request, id=0):
+ is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
+ if not is_ajax:
+ return JSONResponse(
+ status=403, data={
+ 'status': 'false',
+ 'message': 'this view cannot be accessed directly'
+ }
+ )
+ ps = get_object_or_404(PlannedSession, pk=id)
+
+ is_save = request.GET.get('save',0)
+
+ if request.method != 'POST':
+ message = {'status': 'false',
+ 'message': 'this view cannot be accessed through GET'}
+ return JSONResponse(status=403, data=message)
+
+ try:
+ json_data = json.loads(request.body)
+ post_data = json_data
+ except (KeyError, JSONDecodeError):
+ q = request.POST
+ post_data = {k: q.getlist(k) if len(
+ q.getlist(k)) > 1 else v for k, v in q.items()}
+
+ # only allow local host
+ hostt = request.get_host().split(':')
+ if hostt[0] not in ['localhost', '127.0.0.1', 'dev.rowsandall.com', 'rowsandall.com']:
+ message = {'status': 'false',
+ 'message': 'permission denied for host '+hostt[0]}
+ return JSONResponse(status=403, data=message)
+
+ if ps.steps:
+ filename = ps.steps.get('filename','')
+ sport = ps.steps.get('sport','rowing')
+ else:
+ filename = ''
+ sport = 'rowing'
+
+ steps = {
+ "filename": filename,
+ "sport": sport,
+ "steps": []
+ }
+
+ for nr,id in enumerate(post_data):
+ try:
+ step = PlannedSessionStep.objects.get(id=int(id))
+ # add JSON
+ d = step.asdict()
+ d['stepId'] = nr
+ steps['steps'].append(d)
+ except PlannedSessionStep.DoesNotExist:
+ pass
+
+ if is_save:
+ # save the darn thing
+ ps.steps = steps
+
+ ps.interval_string = ''
+ ps.fitfile = None
+ ps.save()
+
+ return JSONResponse(status=200,data=post_data)
+
+@user_passes_test(can_plan, login_url="/rowers/paidplans",
+ message="This functionality requires a Coach or Self-Coach plan",
+ redirect_field_name=None)
+def stepdelete(request, id=0):
+ step = get_object_or_404(PlannedSessionStep, pk=id)
+
+ step.delete()
+
+ backid = request.GET.get('id')
+
+ url = reverse(stepeditor,kwargs={'id':backid})
+
+ return HttpResponseRedirect(url)
+
+@user_passes_test(can_plan, login_url="/rowers/paidplans",
+ message="This functionality requires a Coach or Self-Coach plan",
+ redirect_field_name=None)
+def stepedit(request, id=0, psid=0):
+ step = get_object_or_404(PlannedSessionStep, pk=id)
+ try:
+ ps = PlannedSession.objects.get(id=psid)
+ except PlannedSession.DoesNotExist:
+ ps = None
+
+ form = StepEditorForm(instance=step)
+
+ if request.method == 'POST':
+ form = StepEditorForm(request.POST)
+ if form.is_valid():
+ if ps:
+ dd = step.asdict()
+ dd.pop('stepId')
+ for id,ss in enumerate(ps.steps['steps']):
+ ee = ss.copy()
+ ee.pop('stepId')
+
+ if (dd == ee):
+ ss['durationType'] = form.cleaned_data['durationtype']
+ ss['durationValue'] = form.cleaned_data['durationvalue']
+ ss['targetType'] = form.cleaned_data['targettype']
+ ss['targetValue'] = form.cleaned_data['targetvalue']
+ ss['targetValueLow']= form.cleaned_data['targetvaluelow']
+ ss['targetValueHigh'] = form.cleaned_data['targetvaluehigh']
+ ss['intensity'] = form.cleaned_data['intensity']
+ ss['wkt_step_name'] = form.cleaned_data['name']
+ ss['description'] = form.cleaned_data['description']
+
+ if form.cleaned_data['durationtype'] == 'Time':
+ ss['durationValue'] = form.cleaned_data['durationvalue']*60000
+ elif form.cleaned_data['durationtype'] == 'Distance':
+ ss[durationValue] = form.cleaned_data['durationvalue']*100
+
+ ss['durationValue'] = int(ss['durationValue'])
+ ps.fitfile = None
+ ps.interval_string = ""
+
+ ps.save()
+
+ step.durationtype = form.cleaned_data['durationtype']
+ step.durationvalue = form.cleaned_data['durationvalue']
+ step.targettype = form.cleaned_data['targettype']
+ step.targetvalue = form.cleaned_data['targetvalue']
+ step.targetvaluelow = form.cleaned_data['targetvaluelow']
+ step.targetvaluehigh = form.cleaned_data['targetvaluehigh']
+ step.intensity = form.cleaned_data['intensity']
+ step.name = form.cleaned_data['name']
+ step.description = form.cleaned_data['description']
+
+ if step.durationtype == 'Time':
+ step.durationvalue *= 60000
+ elif step.durationtype == 'Distance':
+ step.durationvalue *= 100
+
+ step.save()
+
+
+ if step.durationtype == 'Time':
+ form.fields['durationvalue'].initial = step.durationvalue / 60000
+ elif step.durationtype == 'Distance':
+ form.fields['durationvalue'].initial = step.durationvalue / 100
+
+
+ stepdescription = step_to_string(step.asdict(), short=False)[0]
+
+ if request.method == 'POST':
+ if 'stepsave_and_return' in request.POST:
+ url = reverse('stepeditor',kwargs = {'id': ps.id})
+ return HttpResponseRedirect(url)
+
+ breadcrumbs = [
+ {
+ 'url': reverse('template_library_view'),
+ 'name': 'Session Templates'
+ },
+ {
+ 'url': reverse('plannedsession_templateedit_view', kwargs={'id': ps.id}),
+ 'name': ps.name
+ },
+ {
+ 'url': reverse('stepeditor', kwargs={'id':ps.id}),
+ 'name': 'Edit Steps'
+ },
+ {
+ 'url': reverse('stepedit', kwargs={'psid': ps.id, 'id': step.id}),
+ 'name': 'Edit Step'
+ }
+ ]
+
+ return render(request,'stepedit.html',
+ {
+ 'step': step,
+ 'stepdescription': stepdescription,
+ 'form': form,
+ 'ps': ps,
+ 'breadcrumbs': breadcrumbs,
+ })
+
+
+
+@user_passes_test(can_plan, login_url="/rowers/paidplans",
+ message="This functionality requires a Coach or Self-Coach plan",
+ redirect_field_name=None)
+def stepeditor(request, id=0):
+ ps = get_object_or_404(PlannedSession, pk=id)
+
+ currentsteps = []
+ if ps.steps:
+ for step in ps.steps['steps']:
+ durationtype = step.get('durationType','')
+ durationvalue = step.get('durationValue',0)
+ targetvalue = step.get('targetValue',0)
+ targettype = step.get('targetType','')
+ targetvaluelow = step.get('targetValueLow',0)
+ targetvaluehigh = step.get('targetValueHigh',0)
+ intensity = step.get('intensity','Active')
+
+ archived_steps = PlannedSessionStep.objects.filter(
+ manager = request.user,
+ durationtype = durationtype,
+ durationvalue = durationvalue,
+ targetvalue = targetvalue,
+ targettype = targettype,
+ targetvaluelow = targetvaluelow,
+ targetvaluehigh = targetvaluehigh,
+ intensity = intensity,
+ )
+ if not archived_steps.count() and durationvalue != 0:
+ s = PlannedSessionStep(
+ manager = request.user,
+ durationtype = durationtype,
+ durationvalue = durationvalue,
+ targetvalue = targetvalue,
+ targettype = targettype,
+ targetvaluelow = targetvaluelow,
+ targetvaluehigh = targetvaluehigh,
+ intensity = intensity,
+ name = step.get('wkt_step_name','Step')
+ )
+ s.save()
+ else:
+ s = archived_steps[0]
+ currentsteps.append(s)
+
+
+ form = StepEditorForm()
+
+ if request.method == 'POST':
+ form = StepEditorForm(request.POST)
+ if form.is_valid():
+ step = form.save(commit=False)
+ step.manager = request.user
+ step.save()
+
+
+ steps = PlannedSessionStep.objects.filter(
+ manager=request.user,
+ durationtype__in=['Time','Distance','RepeatUntilStepsCmplt']
+ ).order_by(
+ 'intensity','-durationvalue','durationtype',
+ )
+
+ stepdescriptions = {}
+
+ for step in steps:
+ stepdescriptions[step.id] = step_to_string(step.asdict(), short=False)[0]
+
+ breadcrumbs = [
+ {
+ 'url': reverse('template_library_view'),
+ 'name': 'Session Templates'
+ },
+ {
+ 'url': reverse('plannedsession_templateedit_view', kwargs={'id': ps.id}),
+ 'name': ps.name
+ },
+ {
+ 'url': reverse('stepeditor', kwargs={'id': ps.id}),
+ 'name': 'Edit Steps'
+ }
+ ]
+
+
+ return render(request, 'stepeditor.html',
+ {
+ 'steps':steps,
+ 'currentsteps': currentsteps,
+ 'stepdescriptions': stepdescriptions,
+ 'form':form,
+ 'ps':ps,
+ 'breadcrumbs': breadcrumbs,
+ })
@user_passes_test(can_plan, login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 8232bfc6..257391fe 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -151,7 +151,8 @@ from rowers.models import (
PlannedSessionComment, CoachRequest, CoachOffer,
VideoAnalysis, ShareKey,
StandardCollection, CourseStandard,
- VirtualRaceFollower, TombStone, InstantPlan
+ VirtualRaceFollower, TombStone, InstantPlan,
+ PlannedSessionStep,
)
from rowers.models import (
RowerPowerForm, RowerHRZonesForm, RowerForm, RowerCPForm, GraphImage, AdvancedWorkoutForm,
@@ -166,7 +167,8 @@ from rowers.models import (
IndoorVirtualRaceResultForm, IndoorVirtualRaceResult,
IndoorVirtualRaceForm, PlannedSessionCommentForm,
Alert, Condition, StaticChartRowerForm,
- FollowerForm, VirtualRaceAthleteForm, InstantPlanForm, DataRowerForm
+ FollowerForm, VirtualRaceAthleteForm, InstantPlanForm, DataRowerForm,
+ StepEditorForm,
)
from rowers.models import (
FavoriteForm, BaseFavoriteFormSet, SiteAnnouncement, BasePlannedSessionFormSet,
diff --git a/static/css/rowsandall2.css b/static/css/rowsandall2.css
index c4d36853..926f970a 100644
--- a/static/css/rowsandall2.css
+++ b/static/css/rowsandall2.css
@@ -322,14 +322,70 @@ th.rotate > div > span {
margin: 2px;
}
+
+.stepcontainer {
+ display: grid;
+ grid-template-columns: 4fr 1fr 4fr;
+ gap: 10px;
+}
+
+.trainingstep {
+ border: 3px solid #666;
+ background-color: #ddd;
+ border-radius: .5em;
+ padding: 10px;
+ cursor: move;
+}
+
+.trainingstep.over {
+ border: 3px dotted #666;
+}
+
+.trainingstep.Warmup {
+ background-color: #ffcccb;
+}
+
+.trainingstep.Cooldown {
+ background-color: #90ee90;
+}
+
+.trainingstep.Rest {
+ background-color: #add8e6;
+}
+
+.RepeatUntilStepsCmplt {
+ background-color: #ffffa7;
+}
+
+.drop-zone {
+ position: relative;
+ overflow: hidden;
+ background-color: #D7D7D7;
+ /* color: white; */
+ padding: 10px;
+ padding-bottom: 40px;
+}
+
+.allcentered {
+ /* Center vertically and horizontally */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.allcenteredchild {
+ margin-top: 50%;
+}
+
.divlines {
- display: block;
- overflow-x: hidden;
- border-width: 1px 0 0 0;
- border-color: #333 #333 #333 #333;
- border-style: solid;
- padding: 0px;
- margin: 0px;
+ width: 50px;
+ height: 50px;
+ background-color: red;
+ /* Center vertically and horizontally */
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin: -25px 0 0 -25px; /* Apply negative top and left margins to truly center the element */
}
.workoutcontainer {