Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add check for variable only arguments #7295

Merged
merged 17 commits into from
Jan 14, 2025
27 changes: 27 additions & 0 deletions app/commands/bootcamp/exercise/available_for_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Bootcamp::Exercise::AvailableForUser
include Mandate

initialize_with :exercise, :user

def call
# If the exercise is gloabally locked, it's locked
return false if exercise.locked?

# Otherwise the previous solution must be completed
previous_exercises_completed?
end

private
delegate :project, to: :exercise

def previous_exercises_completed?
previous_exercises = project.exercises.where.not(id: exercise.id).select do |prev_ex|
prev_ex.level_idx < exercise.level_idx ||
(prev_ex.level_idx == exercise.level_idx && prev_ex.idx < exercise.idx)
end

completed_exercise_ids = user.bootcamp_solutions.completed.where(exercise_id: previous_exercises.map(&:id)).pluck(:exercise_id)

previous_exercises.all? { |ex| completed_exercise_ids.include?(ex.id) }
end
end
20 changes: 4 additions & 16 deletions app/commands/bootcamp/select_next_exercise.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
class Bootcamp::SelectNextExercise
include Mandate

initialize_with :user, project: nil
initialize_with :user

def call
return next_user_project_exercise if next_user_project_exercise

Bootcamp::Exercise.unlocked.where.not(project: user.bootcamp_projects).
Bootcamp::Exercise.unlocked.
where.not(id: completed_exercise_ids).first
end

def user_project
if project
user_project = Bootcamp::UserProject.for!(user, project)
return user_project if user_project.available?
end

user.bootcamp_user_projects.where(status: :available).first
end

private
memoize
def next_user_project_exercise = user_project&.next_exercise

def completed_exercise_ids
user.bootcamp_solutions.where.not(completed_at: nil).select(:exercise_id)
user.bootcamp_solutions.completed.select(:exercise_id)
end
end
8 changes: 7 additions & 1 deletion app/commands/bootcamp/solution/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ class Bootcamp::Solution::Create
initialize_with :user, :exercise

def call
return existing_solution if existing_solution

guard!

begin
Expand All @@ -21,8 +23,12 @@ def call
end

private
def existing_solution
Bootcamp::Solution.find_by(user:, exercise:)
end

def guard!
raise ExerciseLockedError unless user_project.exercise_available?(exercise)
raise ExerciseLockedError unless Bootcamp::Exercise::AvailableForUser.(exercise, user)
end

def code
Expand Down
6 changes: 3 additions & 3 deletions app/commands/bootcamp/update_user_level.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ def call

max = level_idx
end
user.bootcamp_data.update!(level_idx: max)
user.bootcamp_data.update!(level_idx: max + 1)
end

memoize
def exercise_ids_by_level_idx
Bootcamp::Exercise.pluck(:level_idx, :id).
group_by(&:first).
transform_values { |v| v.map(&:last) }.
transform_values { |v| v.map(&:last).sort }.
sort.to_h
end

def solved_exercise_ids_by_level_idx
user.bootcamp_solutions.completed.joins(:exercise).pluck(:level_idx, :exercise_id).
group_by(&:first).
transform_values { |v| v.map(&:last) }
transform_values { |v| v.map(&:last).sort }
end
end
2 changes: 1 addition & 1 deletion app/controllers/api/bootcamp/solutions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class API::Bootcamp::SolutionsController < API::Bootcamp::BaseController
def complete
Bootcamp::Solution::Complete.(@solution)

next_exercise = Bootcamp::SelectNextExercise.(current_user, project: @solution.project)
next_exercise = Bootcamp::SelectNextExercise.(current_user)

render json: {
next_exercise: SerializeBootcampExercise.(next_exercise)
Expand Down
5 changes: 5 additions & 0 deletions app/controllers/bootcamp/base_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class Bootcamp::BaseController < ApplicationController
layout "bootcamp-ui"
before_action :redirect_unless_attendee!
before_action :setup_bootcamp_data!

private
def redirect_unless_attendee!
Expand All @@ -20,6 +21,10 @@ def redirect_unless_attendee!
redirect_to bootcamp_path
end

def setup_bootcamp_data!
current_user.bootcamp_data || current_user.create_bootcamp_data!
end

def use_project
@project = Bootcamp::Project.find_by!(slug: params[:project_slug])
end
Expand Down
5 changes: 4 additions & 1 deletion app/controllers/bootcamp/dashboard_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ class Bootcamp::DashboardController < Bootcamp::BaseController
def index
@exercise = Bootcamp::SelectNextExercise.(current_user)
@solution = current_user.bootcamp_solutions.find_by(exercise: @exercise)
@level = Bootcamp::Level.find_by!(idx: 1)

level_idx = [Bootcamp::Settings.level_idx, current_user.bootcamp_data.level_idx].min
level_idx = 1 if level_idx.zero?
@level = Bootcamp::Level.find_by!(idx: level_idx)
end
end
151 changes: 63 additions & 88 deletions app/css/bootcamp/pages/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,108 +3,83 @@ body.namespace-bootcamp.controller-dashboard.action-index {
}

#page-bootcamp-dashboard {
section.intro {
@apply pt-24;
h1 {
@apply text-[36px] leading-140;
@apply font-bold text-textColor1;
@apply mb-4;
}
h2 {
@apply text-22 leading-150;
@apply font-semibold text-textColor1;
@apply mt-20 mb-4;
}
p.large {
@apply text-20 leading-150;
@apply mb-8;
}
@apply pt-24;
.exercise {
@paply w-full;
@apply py-12 px-16 rounded-8 border-1 border-borderColor5 block;
@apply shadow-base flex flex-col items-stretch;
@apply bg-white;
}
h1 {
@apply text-[36px] leading-140;
@apply font-bold text-textColor1;
@apply mb-4;
}
.level-content {
p,
ul {
@apply text-16 leading-150;
@apply text-20 leading-150;
@apply mb-8;
}
ul {
@apply list-disc pl-20;
}
}
h2 {
@apply text-23 leading-150;
@apply font-semibold text-textColor1;
}
h3 {
@apply text-20 leading-150;
@apply mb-4;
@apply font-semibold;
}
h4 {
@apply text-16 leading-150;
@apply mb-4;
@apply font-semibold;
}
.tag {
@apply flex items-center;
@apply border-1 rounded-100;
@apply font-semibold leading-170;
@apply px-12 py-4 ml-auto;
@apply whitespace-nowrap;

section.normal {
@apply pt-24;
.exercise {
@paply w-full;
@apply py-12 px-16 rounded-8 border-1 border-borderColor5 block;
@apply shadow-base flex flex-col items-stretch;
@apply bg-white;
}
h1 {
@apply text-[36px] leading-140;
@apply font-bold text-textColor1;
@apply mb-4;
&.completed {
background: #e7fdf6;
border-color: #43b593;
color: #43b593;
}
p.large {
@apply text-20 leading-150;
@apply mb-8;
}
h2 {
@apply text-23 leading-150;
@apply font-semibold text-textColor1;
}
p {
@apply text-16 leading-140;
}

.c-youtube-container {
@apply mt-20;
iframe {
@apply w-full;
}
h3 {
@apply text-20 leading-150;
@apply mb-4;
@apply font-semibold;
}
}
.rhs {
.section-link {
@apply border-1 border-borderColor5 block rounded-5 px-20 py-12 bg-white shadow-sm;
@apply flex gap-16;
img {
@apply w-[40px] h-[40px];
}
h4 {
@apply text-16 leading-150;
@apply mb-4;
@apply font-semibold;
}
.tag {
@apply flex items-center;
@apply border-1 rounded-100;
@apply font-semibold leading-170;
@apply px-12 py-4 ml-auto;
@apply whitespace-nowrap;

&.completed {
background: #e7fdf6;
border-color: #43b593;
color: #43b593;
}
@apply text-18 font-semibold mb-2;
}
p {
@apply text-16 leading-140;
}

.c-youtube-container {
@apply mt-20;
iframe {
@apply w-full;
}
@apply text-15 leading-140;
}
}
.rhs {
.section-link {
@apply border-1 border-borderColor5 block rounded-5 px-20 py-12 bg-white shadow-sm;
@apply flex gap-16;
img {
@apply w-[40px] h-[40px];
}
h4 {
@apply text-18 font-semibold mb-2;
}
p {
@apply text-15 leading-140;
}
}

.level-number {
@apply rounded-circle text-[24px] leading-140;
@apply grid place-items-center flex-shrink-0;
@apply border-3 border-purple;
@apply text-purple font-bold;
@apply w-[48px] h-[48px];
}
.level-number {
@apply rounded-circle text-[24px] leading-140;
@apply grid place-items-center flex-shrink-0;
@apply border-3 border-purple;
@apply text-purple font-bold;
@apply w-[48px] h-[48px];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class AnimationTimeline {

public populateTimeline(animations: Animation[]) {
animations.forEach((animation: Animation) => {
// console.log(animation.offset)
this.animationTimeline.add(
{ ...animation, ...animation.transformations },
animation.offset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export function placeholderExtension() {
}

function generateArrowSVG(length: number, right: number, hoverRight: number) {
console.log(hoverRight)
let offset = 30
right = right + offset
length = length + 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type { StaticError } from '@/interpreter/error'
import { INFO_HIGHLIGHT_COLOR } from '../CodeMirror/extensions/lineHighlighter'
import { scrollToHighlightedLine } from './scrollToHighlightedLine'

const FRAME_DURATION = 50

export function useScrubber({
setIsPlaying,
testResult,
Expand All @@ -32,7 +34,7 @@ export function useScrubber({
testResult.animationTimeline.onUpdate((anime) => {
setTimeout(() => {
setValue(anime.currentTime)
}, 50)
}, FRAME_DURATION)
})
}
}, [testResult.view?.id, testResult.animationTimeline?.completed])
Expand Down Expand Up @@ -155,7 +157,7 @@ export function useScrubber({
targets: { value },
// if progress is closer to duration than time, then snap to duration
value: closestTime,
duration: 50,
duration: FRAME_DURATION,
easing: 'easeOutQuad',
update: function (anim) {
const newTime = Number(anim.animations[0].currentValue)
Expand Down Expand Up @@ -214,7 +216,7 @@ export function useScrubber({
scrubberValueAnimation.current = anime({
targets: { value },
value: targetTime,
duration: 50,
duration: FRAME_DURATION,
easing: 'easeOutQuad',
update: function (anim) {
const animatedTime = Number(anim.animations[0].currentValue)
Expand Down Expand Up @@ -253,7 +255,7 @@ export function useScrubber({
scrubberValueAnimation.current = anime({
targets: { value },
value: targetTime,
duration: 50,
duration: FRAME_DURATION,
easing: 'easeOutQuad',
update: function (anim) {
const animatedTime = Number(anim.animations[0].currentValue)
Expand Down
Loading
Loading