diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json deleted file mode 100644 index bc04b3c..0000000 --- a/.config/dotnet-tools.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "dotnet-stryker": { - "version": "3.6.0", - "commands": [ - "dotnet-stryker" - ] - }, - "csharpier": { - "version": "0.25.0", - "commands": [ - "dotnet-csharpier" - ] - } - } -} \ No newline at end of file diff --git a/.csharpierrc.json b/.csharpierrc.json deleted file mode 100644 index 321cc8b..0000000 --- a/.csharpierrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "printWidth": 100, - "useTabs": false, - "tabWidth": 2 -} \ No newline at end of file diff --git a/nextjs/.dockerignore b/.dockerignore similarity index 100% rename from nextjs/.dockerignore rename to .dockerignore diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 1e15504..0000000 --- a/.editorconfig +++ /dev/null @@ -1,348 +0,0 @@ -root = true - -# All files -[*] -indent_style = space -indent_size = 2 -end_of_line = lf - -# C# files -[*.cs] -tab_width = 2 -insert_final_newline = false - -[*.{cs,vb}] - -# Organize usings -dotnet_separate_import_directive_groups = true -dotnet_sort_system_directives_first = true -file_header_template = unset - -# this. and Me. preferences -dotnet_style_qualification_for_event = false:silent -dotnet_style_qualification_for_field = false:silent -dotnet_style_qualification_for_method = false:silent -dotnet_style_qualification_for_property = false:silent - -# Language keywords vs BCL types preferences -dotnet_style_predefined_type_for_locals_parameters_members = true:silent -dotnet_style_predefined_type_for_member_access = true:silent - -# Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent - -# Modifier preferences -dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent - -# Expression-level preferences -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_object_initializer = true:suggestion -dotnet_style_operator_placement_when_wrapping = beginning_of_line -dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_prefer_compound_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion -dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion -dotnet_style_prefer_inferred_tuple_names = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion -dotnet_style_prefer_simplified_boolean_expressions = true:suggestion -dotnet_style_prefer_simplified_interpolation = true:suggestion - -# Field preferences -dotnet_style_readonly_field = true:warning - -# Parameter preferences -dotnet_code_quality_unused_parameters = all:suggestion - -# Suppression preferences -dotnet_remove_unnecessary_suppression_exclusions = none - -#### C# Coding Conventions #### -[*.cs] - -# var preferences -csharp_style_var_elsewhere = false:silent -csharp_style_var_for_built_in_types = false:silent -csharp_style_var_when_type_is_apparent = false:silent - -# Expression-bodied members -csharp_style_expression_bodied_accessors = true:silent -csharp_style_expression_bodied_constructors = false:silent -csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_lambdas = true:suggestion -csharp_style_expression_bodied_local_functions = false:silent -csharp_style_expression_bodied_methods = false:silent -csharp_style_expression_bodied_operators = false:silent -csharp_style_expression_bodied_properties = true:silent - -# Pattern matching preferences -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_prefer_not_pattern = true:suggestion -csharp_style_prefer_pattern_matching = true:silent -csharp_style_prefer_switch_expression = true:suggestion - -# Null-checking preferences -csharp_style_conditional_delegate_call = true:suggestion - -# Modifier preferences -csharp_prefer_static_local_function = true:warning -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent - -# Code-block preferences -csharp_prefer_braces = true:silent -csharp_prefer_simple_using_statement = true:suggestion - -# Expression-level preferences -csharp_prefer_simple_default_expression = true:suggestion -csharp_style_deconstructed_variable_declaration = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion -csharp_style_pattern_local_over_anonymous_function = true:suggestion -csharp_style_prefer_index_operator = true:suggestion -csharp_style_prefer_range_operator = true:suggestion -csharp_style_throw_expression = true:suggestion -csharp_style_unused_value_assignment_preference = discard_variable:suggestion -csharp_style_unused_value_expression_statement_preference = discard_variable:silent - -# 'using' directive preferences -csharp_using_directive_placement = outside_namespace:silent - -#### C# Formatting Rules #### - -# New line preferences -csharp_new_line_before_catch = true -csharp_new_line_before_else = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_open_brace = all -csharp_new_line_between_query_expression_clauses = true - -# Indentation preferences -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true -csharp_indent_labels = one_less_than_current -csharp_indent_switch_labels = true - -# Space preferences -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - -# Wrapping preferences -csharp_preserve_single_line_blocks = true -csharp_preserve_single_line_statements = true - -#### Naming styles #### -[*.{cs,vb}] - -# Naming rules - -dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces -dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion -dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces -dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase - -dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion -dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters -dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase - -dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods -dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties -dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.events_should_be_pascalcase.symbols = events -dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion -dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables -dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase - -dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion -dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants -dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase - -dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion -dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters -dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase - -dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields -dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.private_fields_should_be_s_camelcase.severity = suggestion -dotnet_naming_rule.private_fields_should_be_s_camelcase.symbols = private_fields -dotnet_naming_rule.private_fields_should_be_s_camelcase.style = camelcase - -dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion -dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields -dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase - -dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields -dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields -dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields -dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields -dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums -dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions -dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase - -dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion -dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members -dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase - -# Symbol specifications - -dotnet_naming_symbols.interfaces.applicable_kinds = interface -dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interfaces.required_modifiers = - -dotnet_naming_symbols.enums.applicable_kinds = enum -dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.enums.required_modifiers = - -dotnet_naming_symbols.events.applicable_kinds = event -dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.events.required_modifiers = - -dotnet_naming_symbols.methods.applicable_kinds = method -dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.methods.required_modifiers = - -dotnet_naming_symbols.properties.applicable_kinds = property -dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.properties.required_modifiers = - -dotnet_naming_symbols.public_fields.applicable_kinds = field -dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_fields.required_modifiers = - -dotnet_naming_symbols.private_fields.applicable_kinds = field -dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_fields.required_modifiers = - -dotnet_naming_symbols.private_static_fields.applicable_kinds = field -dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_static_fields.required_modifiers = static - -dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum -dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types_and_namespaces.required_modifiers = - -dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method -dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = - -dotnet_naming_symbols.type_parameters.applicable_kinds = namespace -dotnet_naming_symbols.type_parameters.applicable_accessibilities = * -dotnet_naming_symbols.type_parameters.required_modifiers = - -dotnet_naming_symbols.private_constant_fields.applicable_kinds = field -dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_constant_fields.required_modifiers = const - -dotnet_naming_symbols.local_variables.applicable_kinds = local -dotnet_naming_symbols.local_variables.applicable_accessibilities = local -dotnet_naming_symbols.local_variables.required_modifiers = - -dotnet_naming_symbols.local_constants.applicable_kinds = local -dotnet_naming_symbols.local_constants.applicable_accessibilities = local -dotnet_naming_symbols.local_constants.required_modifiers = const - -dotnet_naming_symbols.parameters.applicable_kinds = parameter -dotnet_naming_symbols.parameters.applicable_accessibilities = * -dotnet_naming_symbols.parameters.required_modifiers = - -dotnet_naming_symbols.public_constant_fields.applicable_kinds = field -dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_constant_fields.required_modifiers = const - -dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field -dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static - -dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field -dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static - -dotnet_naming_symbols.local_functions.applicable_kinds = local_function -dotnet_naming_symbols.local_functions.applicable_accessibilities = * -dotnet_naming_symbols.local_functions.required_modifiers = - -# Naming styles - -dotnet_naming_style.pascalcase.required_prefix = -dotnet_naming_style.pascalcase.required_suffix = -dotnet_naming_style.pascalcase.word_separator = -dotnet_naming_style.pascalcase.capitalization = pascal_case - -dotnet_naming_style.ipascalcase.required_prefix = I -dotnet_naming_style.ipascalcase.required_suffix = -dotnet_naming_style.ipascalcase.word_separator = -dotnet_naming_style.ipascalcase.capitalization = pascal_case - -dotnet_naming_style.tpascalcase.required_prefix = T -dotnet_naming_style.tpascalcase.required_suffix = -dotnet_naming_style.tpascalcase.word_separator = -dotnet_naming_style.tpascalcase.capitalization = pascal_case - -dotnet_naming_style.camelcase.required_prefix = -dotnet_naming_style.camelcase.required_suffix = -dotnet_naming_style.camelcase.word_separator = -dotnet_naming_style.camelcase.capitalization = camel_case - -dotnet_naming_style.s_camelcase.required_prefix = s_ -dotnet_naming_style.s_camelcase.required_suffix = -dotnet_naming_style.s_camelcase.word_separator = -dotnet_naming_style.s_camelcase.capitalization = camel_case - diff --git a/nextjs/.eslintrc.json b/.eslintrc.json similarity index 100% rename from nextjs/.eslintrc.json rename to .eslintrc.json diff --git a/.gitignore b/.gitignore index 03614c2..a692a4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,47 @@ -obj/ -bin/ -.env -*.env -storage/ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +.pnpm-store/ tmp.json -tmp*.json -.vs/ \ No newline at end of file + +**/*.env +.env +**/*.env.test + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + + +storage/ +temp/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 327e78a..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "recommendations": [ - ] -} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index ec27518..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/Management.Web/bin/Debug/net8.0/Management.Web.dll", - "args": [], - "cwd": "${workspaceFolder}/Management.Web", - "stopAtEntry": false, - // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser - "serverReadyAction": { - "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)" - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceFolder}/Views" - } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 53926d5..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "dotnet.defaultSolution": "canvasManagement.sln" -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 0917445..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/canvasManagement.sln", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/canvasManagement.sln", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/canvasManagement.sln" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/nextjs/Dockerfile b/Dockerfile similarity index 100% rename from nextjs/Dockerfile rename to Dockerfile diff --git a/Management.Test/CanvasModels/TermTests.cs b/Management.Test/CanvasModels/TermTests.cs deleted file mode 100644 index a348d11..0000000 --- a/Management.Test/CanvasModels/TermTests.cs +++ /dev/null @@ -1,29 +0,0 @@ - -using CanvasModel.EnrollmentTerms; - -public class DeserializationTests -{ - [Fact] - public void TestTerm() - { - - var canvasContentResponse = @"{ - ""enrollment_terms"": [ - { - ""id"": 1, - ""name"": ""one"", - ""start_at"": ""2022-01-01T00:00:00Z"", - ""end_at"": ""2022-02-01T00:00:00Z"", - ""created_at"": ""2011-04-26T23:34:35Z"", - ""workflow_state"": ""active"", - ""grading_period_group_id"": null - } - ] - }"; - - var result = JsonSerializer.Deserialize(canvasContentResponse); - - result.Should().NotBeNull(); - result?.EnrollmentTerms?.First().Id.Should().Be(1); - } -} diff --git a/Management.Test/Features/CalendarMonthTests.cs b/Management.Test/Features/CalendarMonthTests.cs deleted file mode 100644 index 3d4e4c5..0000000 --- a/Management.Test/Features/CalendarMonthTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -public class CalendarMonthTests -{ - [Fact] - public void TestCalendarMonthCanGetFirstWeek() - { - var month = new CalendarMonth(2023, 2); - - int?[] expectedFirstWeek = new int?[] { - null, null, null, 1, 2, 3, 4 - }; - - month.Weeks.First().Should().BeEquivalentTo(expectedFirstWeek); - } - - [Fact] - public void TestCanGetAnotherMonthsFirstWeek() - { - var month = new CalendarMonth(2023, 4); - - int?[] expectedFirstWeek = new int?[] { - null, null, null, null, null, null, 1 - }; - - month.Weeks.First().Should().BeEquivalentTo(expectedFirstWeek); - } - - [Fact] - public void TestCorrectNumberOfWeeks() - { - var month = new CalendarMonth(2023, 4); - - month.Weeks.Count().Should().Be(6); - } - - [Fact] - public void TestLastWeekIsCorrect() - { - var month = new CalendarMonth(2023, 4); - int?[] expectedLastWeek = new int?[] { - 30, null, null, null, null, null, null, - }; - - month.Weeks.Last().Should().BeEquivalentTo(expectedLastWeek); - } -} diff --git a/Management.Test/Features/ConfigurationTests.cs b/Management.Test/Features/ConfigurationTests.cs deleted file mode 100644 index 2276028..0000000 --- a/Management.Test/Features/ConfigurationTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// using CanvasModel.EnrollmentTerms; - -// public class ConfigurationTests -// { -// [Fact] -// public void TestCanCreateConfigFromTermAndDays() -// { -// DateTime startAt = new DateTime(2022, 1, 1); -// DateTime endAt = new DateTime(2022, 1, 2); -// var canvasTerm = new EnrollmentTermModel( -// Id: 1, -// Name: "one", -// StartAt: startAt, -// EndAt: endAt -// ); -// var daysOfWeek = new DayOfWeek[] { DayOfWeek.Monday }; -// var management = new CoursePlanner(); -// management.SetConfiguration(canvasTerm, daysOfWeek); -// var config = management.SemesterCalendar; - -// if(config == null) Assert.Fail(); -// config!.StartDate.Should().Be(startAt); -// config!.EndDate.Should().Be(endAt); -// config!.Days.Should().BeEquivalentTo(daysOfWeek); -// } -// } diff --git a/Management.Test/Features/ModuleTests.cs b/Management.Test/Features/ModuleTests.cs deleted file mode 100644 index 732ac41..0000000 --- a/Management.Test/Features/ModuleTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// public class ModuleTests -// { -// [Fact] -// public void CanAddModule() -// { -// var manager = new ModuleManager(); -// var module = new CourseModule("First Module", new LocalAssignment[] { }); -// manager.AddModule(module); - -// manager.Modules.Count().Should().Be(1); -// manager.Modules.First().Should().Be(module); -// } - -// [Fact] -// public void CanAddAssignmentToCorrectModule() -// { -// var manager = new ModuleManager(); -// manager.AddModule(new CourseModule("First Module", new LocalAssignment[] { })); -// manager.AddModule(new CourseModule("Second Module", new LocalAssignment[] { })); -// manager.AddModule(new CourseModule("Third Module", new LocalAssignment[] { })); -// manager.AddModule(new CourseModule("Fourth Module", new LocalAssignment[] { })); - -// var assignment = new LocalAssignment -// { -// name = "testname", -// description = "testDescription", -// published = false, -// lock_at_due_date = true, -// rubric = new RubricItem[] { }, -// lock_at = null, -// due_at = DateTime.Now, -// points_possible = 10, -// submission_types = new SubmissionType[] { SubmissionType.online_text_entry } -// }; - -// manager.AddAssignment(3, assignment); -// manager.Modules.Count().Should().Be(4); -// manager.Modules.ElementAt(3).Assignments.Count().Should().Be(1); -// } -// } diff --git a/Management.Test/Features/SemesterPlannerTests.cs b/Management.Test/Features/SemesterPlannerTests.cs deleted file mode 100644 index 6409eb4..0000000 --- a/Management.Test/Features/SemesterPlannerTests.cs +++ /dev/null @@ -1,97 +0,0 @@ -// using CanvasModel.EnrollmentTerms; - -// namespace Management.Test; - -// public class SemesterPlannerTests -// { -// [Fact] -// public void TestCanCreatePlanner() -// { - -// var config = new SemesterCalendarConfig( -// StartDate: new DateTime(2022, 1, 1), -// EndDate: new DateTime(2022, 1, 2), -// new DayOfWeek[] { } -// ); - -// var semester = new SemesterPlanner(config); - -// semester.Months.Count().Should().Be(1); -// } - -// [Fact] -// public void TestNewPlannerHasCorrectNumberOfMonths() -// { -// var config = new SemesterCalendarConfig( -// StartDate: new DateTime(2022, 1, 1), -// EndDate: new DateTime(2022, 2, 1), -// new DayOfWeek[] { } -// ); - -// var semester = new SemesterPlanner(config); - -// semester.Months.Count().Should().Be(2); -// } - -// [Fact] -// public void TestNewPlannerHandlesTermsThatWrapYears() -// { -// var config = new SemesterCalendarConfig( -// StartDate: new DateTime(2022, 12, 1), -// EndDate: new DateTime(2023, 1, 1), -// new DayOfWeek[] { } -// ); - -// var semester = new SemesterPlanner(config); - -// semester.Months.Count().Should().Be(2); -// } - -// [Fact] -// public void TestSemesterGetsCorrectMonths() -// { -// var config = new SemesterCalendarConfig( -// StartDate: new DateTime(2022, 1, 1), -// EndDate: new DateTime(2022, 2, 1), -// new DayOfWeek[] { } -// ); - -// var semester = new SemesterPlanner(config); - -// semester.Months.First().Month.Should().Be(1); -// semester.Months.Last().Month.Should().Be(2); -// } - - -// [Fact] -// public void TestMonthsCanWrapYears() -// { -// var config = new SemesterCalendarConfig( -// StartDate: new DateTime(2022, 12, 1), -// EndDate: new DateTime(2023, 1, 1), -// new DayOfWeek[] { } -// ); - -// var semester = new SemesterPlanner(config); - -// semester.Months.First().Month.Should().Be(12); -// semester.Months.First().Year.Should().Be(2022); - -// semester.Months.Last().Month.Should().Be(1); -// semester.Months.Last().Year.Should().Be(2023); -// } - -// [Fact] -// public void TestSemesterTracksDaysOfWeek() -// { -// DayOfWeek[] days = new DayOfWeek[] { DayOfWeek.Monday }; -// var config = new SemesterCalendarConfig( -// StartDate: new DateTime(2022, 12, 1), -// EndDate: new DateTime(2023, 1, 1), -// days -// ); - -// var semester = new SemesterPlanner(config); -// semester.Days.Should().BeEquivalentTo(days); -// } -// } diff --git a/Management.Test/FileStorage/CouresDifferencesChangesTests.cs b/Management.Test/FileStorage/CouresDifferencesChangesTests.cs deleted file mode 100644 index 652b5dd..0000000 --- a/Management.Test/FileStorage/CouresDifferencesChangesTests.cs +++ /dev/null @@ -1,515 +0,0 @@ - - -using LocalModels; - -public class CouresDifferencesChangesTests -{ - [Fact] - public void CanDetectNewSettings() - { - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [] - }; - LocalCourse newCourse = new() - { - Settings = new() { Name = "new course name" }, - Modules = [] - }; - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - - differences.Modules.Should().BeEmpty(); - differences.Settings.Should().NotBeNull(); - differences.Settings?.Name.Should().Be("new course name"); - } - - [Fact] - public void CanDetectNewModule() - { - - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [] - }; - LocalCourse newCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [ - new() - { - Name = "new module", - } - ] - }; - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - - differences.Modules.Should().NotBeNull(); - differences.Modules?.Count().Should().Be(1); - differences.Modules?.First().Name.Should().Be("new module"); - } - - [Fact] - public void CanDetectChangedAssignment() - { - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [ - new() - { - Name = "new module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "", - DueAt = new DateTime(), - SubmissionTypes = [], - Rubric = [] - } - ] - }] - }; - LocalCourse newCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [ - new() - { - Name = "new module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "new description", - DueAt = new DateTime(), - SubmissionTypes = [], - Rubric = [] - } - ] - } - ] - }; - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - - differences.Modules.Should().NotBeNull(); - differences.Modules?.Count().Should().Be(1); - differences.Modules?.First().Assignments.First().Description.Should().Be("new description"); - } - - [Fact] - public void CanProperlyIgnoreUnchangedModules() - { - var commonDate = new DateTime(); - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [new() - { - Name = "new module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "", - DueAt = commonDate, - SubmissionTypes = [], - Rubric = [] - } - ] - }] - }; - LocalCourse newCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [new() - { - Name = "new module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "", - DueAt = commonDate, - SubmissionTypes = [], - Rubric = [] - } - ] - }] - }; - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - - differences.Modules.Should().BeEmpty(); - } - - [Fact] - public void OnlyChangedAssignmentRepresented() - { - var commonDate = new DateTime(); - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [new() - { - Name = "new module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "", - DueAt = commonDate, - SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], - Rubric = [ new() {Points = 1, Label = "rubric"} ], - }, - new() - { - Name = "test assignment 2", - Description = "", - DueAt = commonDate, - SubmissionTypes = [], - Rubric = [], - } - ] - }] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new() - { - Name = "new module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "", - DueAt = commonDate, - SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], - Rubric = [ new() {Points = 1, Label = "rubric"} ], - }, - new() - { - Name = "test assignment 2 with a new name", - Description = "", - DueAt = commonDate, - SubmissionTypes = [], - Rubric = [] - } - ] - } - ] - }; - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - - differences.Modules.First().Assignments.Count().Should().Be(1); - differences.Modules.First().Assignments.First().Name.Should().Be("test assignment 2 with a new name"); - } - - [Fact] - public void IdenticalQuizzesIgnored() - { - var commonDate = new DateTime(); - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [new(){ - Name = "new module", - Assignments = [], - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = @"this is my description ", - LockAt = commonDate, - DueAt = commonDate, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = [] - } - ] - }] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [new(){ - Name = "new module", - Assignments = [], - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = @"this is my description ", - LockAt = commonDate, - DueAt = commonDate, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = [] - } - ] - }] - }; - - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - differences.Modules.Count().Should().Be(0); - } - - [Fact] - public void CanDetectDifferentQuiz() - { - var commonDate = new DateTime(); - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [new(){ - Name = "new module", - Assignments = [], - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = @"this is my description ", - LockAt = commonDate, - DueAt = commonDate, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = [] - } - ] - }] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [new(){ - Name = "new module", - Assignments = [], - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = @"this is my description ", - LockAt = DateTime.MaxValue, - DueAt = commonDate, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = [] - } - ] - }] - }; - - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - differences.Modules.Count().Should().Be(1); - differences.Modules.First().Quizzes.Count().Should().Be(1); - differences.Modules.First().Quizzes.First().LockAt.Should().Be(DateTime.MaxValue); - } - - [Fact] - public void CanDetectOnlyDifferentQuiz_WhenOtherQuizzesStay() - { - var commonDate = new DateTime(); - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [new(){ - Name = "new module", - Assignments = [], - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = @"this is my description ", - LockAt = commonDate, - DueAt = commonDate, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = [] - } - ] - }] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [new(){ - Name = "new module", - Assignments = [], - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = @"this is my description ", - LockAt = commonDate, - DueAt = commonDate, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = [] - }, - new() - { - Name = "Test Quiz 2", - Description = @"this is my description ", - LockAt = commonDate, - DueAt = commonDate, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = [] - } - ] - }] - }; - - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - differences.Modules.Count().Should().Be(1); - differences.Modules.First().Quizzes.Count().Should().Be(1); - differences.Modules.First().Quizzes.First().Name.Should().Be("Test Quiz 2"); - } - - [Fact] - public void SamePagesNotDetected() - { - - var commonDate = new DateTime(); - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [new(){ - Name = "new module", - Pages = [ - new() - { - Name= "test page", - Text = "test description", - DueAt = commonDate - } - ] - }] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new(){ - Name = "new module", - Pages = [ - new() - { - Name= "test page", - Text = "test description", - DueAt = commonDate - } - ] - } - ] - }; - - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - differences.Modules.Count().Should().Be(0); - } - - [Fact] - public void DifferentPageDetected() - { - - var commonDate = new DateTime(); - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [new(){ - Name = "new module", - Pages = [ - new() - { - Name= "test page", - Text = "test description", - DueAt = commonDate - } - ] - }] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new(){ - Name = "new module", - Pages = [ - new() - { - Name= "test page", - Text = "test description changed", - DueAt = commonDate - } - ] - } - ] - }; - - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - differences.Modules.Count().Should().Be(1); - differences.Modules.First().Pages.Count().Should().Be(1); - differences.Modules.First().Pages.First().Text.Should().Be("test description changed"); - } - - [Fact] - public void DifferentPageDetected_ButNotSamePage() - { - - var commonDate = new DateTime(); - LocalCourse oldCourse = new() - { - Settings = new() { Name = "Test Course" }, - Modules = [new(){ - Name = "new module", - Pages = [ - new() - { - Name= "test page", - Text = "test description", - DueAt = commonDate - } - ] - }] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new(){ - Name = "new module", - Pages = [ - new() - { - Name= "test page", - Text = "test description", - DueAt = commonDate - }, - new() - { - Name= "test page 2", - Text = "test description", - DueAt = commonDate - } - ] - } - ] - }; - - var differences = CourseDifferences.GetNewChanges(newCourse, oldCourse); - differences.Modules.Count().Should().Be(1); - differences.Modules.First().Pages.Count().Should().Be(1); - differences.Modules.First().Pages.First().Name.Should().Be("test page 2"); - } - -} \ No newline at end of file diff --git a/Management.Test/FileStorage/CourseDifferencesDeletionsTests.cs b/Management.Test/FileStorage/CourseDifferencesDeletionsTests.cs deleted file mode 100644 index fadcbed..0000000 --- a/Management.Test/FileStorage/CourseDifferencesDeletionsTests.cs +++ /dev/null @@ -1,297 +0,0 @@ -using LocalModels; - -public class CourseDifferencesDeletionsTests -{ - [Fact] - public void SameModuleDoesNotGetDeleted() - { - LocalCourse oldCourse = new() - { - Settings = new() { }, - Modules = [ - new() - { - Name = "test module" - }] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new() - { - Name = "test module" - }] - }; - - var differences = CourseDifferences.GetDeletedChanges(newCourse, oldCourse); - - differences.NamesOfModulesToDeleteCompletely.Should().BeEmpty(); - } - [Fact] - public void ChangedModule_OldOneGetsDeleted() - { - LocalCourse oldCourse = new() - { - Settings = new() { }, - Modules = [ - new() - { - Name = "test module" - } - ] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new() - { - Name = "test module 2" - }] - }; - - var differences = CourseDifferences.GetDeletedChanges(newCourse, oldCourse); - - differences.NamesOfModulesToDeleteCompletely.Count().Should().Be(1); - differences.NamesOfModulesToDeleteCompletely.First().Should().Be("test module"); - } - - [Fact] - public void newAssignmentNameGetsDeleted() - { - LocalCourse oldCourse = new() - { - Settings = new() { }, - Modules = [ - new() - { - Name = "test module", - Assignments = [ - new() - { - Name = "test assignment" - } - ] - } - ] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new() - { - Name = "test module", - Assignments = [ - new() - { - Name = "test assignment changed name" - } - ] - }] - }; - - var differences = CourseDifferences.GetDeletedChanges(newCourse, oldCourse); - - differences.NamesOfModulesToDeleteCompletely.Should().BeEmpty(); - differences.DeleteContentsOfModule.Count().Should().Be(1); - differences.DeleteContentsOfModule.First().Assignments.Count().Should().Be(1); - differences.DeleteContentsOfModule.First().Assignments.First().Name.Should().Be("test assignment"); - } - [Fact] - public void AssignmentsWithChangedDescriptionsDoNotGetDeleted() - { - LocalCourse oldCourse = new() - { - Settings = new() { }, - Modules = [ - new() - { - Name = "test module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "test description", - } - ] - } - ] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new() - { - Name = "test module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "test description", - } - ] - }] - }; - - var differences = CourseDifferences.GetDeletedChanges(newCourse, oldCourse); - - differences.DeleteContentsOfModule.Should().BeEmpty(); - } - [Fact] - public void CanDetectChangedAndUnchangedAssignments() - { - LocalCourse oldCourse = new() - { - Settings = new() { }, - Modules = [ - new() - { - Name = "test module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "test description", - }, - new() - { - Name = "test assignment 2", - Description = "test description", - } - ] - } - ] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new() - { - Name = "test module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "test description", - }, - new() - { - Name = "test assignment 2 changed", - Description = "test description", - } - ] - }] - }; - - var differences = CourseDifferences.GetDeletedChanges(newCourse, oldCourse); - - differences.DeleteContentsOfModule.Count().Should().Be(1); - differences.DeleteContentsOfModule.First().Assignments.Count().Should().Be(1); - differences.DeleteContentsOfModule.First().Assignments.First().Name.Should().Be("test assignment 2"); - } - - [Fact] - public void ChangedQuizzesGetDeleted() - { - LocalCourse oldCourse = new() - { - Settings = new() { }, - Modules = [ - new() - { - Name = "test module", - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = "test description" - }, - new() - { - Name = "Test Quiz 2", - Description = "test description" - } - ] - } - ] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new() - { - Name = "test module", - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = "test description" - }, - new() - { - Name = "Test Quiz 3", - Description = "test description" - } - ] - }] - }; - - var differences = CourseDifferences.GetDeletedChanges(newCourse, oldCourse); - - differences.DeleteContentsOfModule.Count().Should().Be(1); - differences.DeleteContentsOfModule.First().Quizzes.Count().Should().Be(1); - differences.DeleteContentsOfModule.First().Quizzes.First().Name.Should().Be("Test Quiz 2"); - } - - [Fact] - public void ChangedPagesGetDeleted() - { - LocalCourse oldCourse = new() - { - Settings = new() { }, - Modules = [ - new() - { - Name = "test module", - Pages = [ - new() - { - Name = "Test Page", - Text = "test contents" - }, - new() - { - Name = "Test Page 2", - Text = "test contents" - }, - ] - } - ] - }; - LocalCourse newCourse = oldCourse with - { - Modules = [ - new() - { - Name = "test module", - Pages = [ - new() - { - Name = "Test Page", - Text = "test contents" - }, - new() - { - Name = "Test Page 3", - Text = "test contents" - }, - ] - }] - }; - - var differences = CourseDifferences.GetDeletedChanges(newCourse, oldCourse); - - differences.DeleteContentsOfModule.Count().Should().Be(1); - differences.DeleteContentsOfModule.First().Pages.Count().Should().Be(1); - differences.DeleteContentsOfModule.First().Pages.First().Name.Should().Be("Test Page 2"); - } -} \ No newline at end of file diff --git a/Management.Test/FileStorage/FileStorageTests.cs b/Management.Test/FileStorage/FileStorageTests.cs deleted file mode 100644 index bcd9523..0000000 --- a/Management.Test/FileStorage/FileStorageTests.cs +++ /dev/null @@ -1,301 +0,0 @@ -using System.Configuration; -using LocalModels; -using Management.Services; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using NSubstitute; - -public class FileStorageTests -{ - private FileStorageService fileManager { get; set; } - - public FileStorageTests() - { - var tempDirectory = Path.GetTempPath(); - var storageDirectory = tempDirectory + "fileStorageTests"; - Console.WriteLine(storageDirectory); - if (!Directory.Exists(storageDirectory)) - Directory.CreateDirectory(storageDirectory); - else - { - var dirInfo = new DirectoryInfo(storageDirectory); - - foreach (var file in dirInfo.GetFiles()) - file.Delete(); - foreach (var dir in dirInfo.GetDirectories()) - dir.Delete(true); - } - - var fileManagerLogger = new MyLogger(NullLogger.Instance); - var markdownLoaderLogger = new MyLogger(NullLogger.Instance); - var markdownSaverLogger = new MyLogger(NullLogger.Instance); - var otherLogger = NullLoggerFactory.Instance.CreateLogger(); - Environment.SetEnvironmentVariable("storageDirectory", storageDirectory); - var config = new ConfigurationBuilder() - .AddEnvironmentVariables() - .Build(); - var fileConfiguration = new FileConfiguration(config); - - var markdownLoader = new CourseMarkdownLoader(markdownLoaderLogger, fileConfiguration); - var markdownSaver = new MarkdownCourseSaver(markdownSaverLogger, fileConfiguration); - fileManager = new FileStorageService(fileManagerLogger, markdownLoader, markdownSaver, otherLogger, fileConfiguration); - } - - [Fact] - public async Task EmptyCourse_CanBeSavedAndLoaded() - { - LocalCourse testCourse = new LocalCourse - { - Settings = new() { Name = "test empty course" }, - Modules = [] - }; - - await fileManager.SaveCourseAsync(testCourse, null); - - var loadedCourses = await fileManager.LoadSavedCourses(); - var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); - - loadedCourse.Should().BeEquivalentTo(testCourse); - } - - [Fact] - public async Task CourseSettings_CanBeSavedAndLoaded() - { - LocalCourse testCourse = new() - { - Settings = new() - { - AssignmentGroups = [], - Name = "Test Course with settings", - DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday], - StartDate = new DateTime(), - EndDate = new DateTime(), - DefaultDueTime = new() { Hour = 1, Minute = 59 }, - }, - Modules = [] - }; - - await fileManager.SaveCourseAsync(testCourse, null); - - var loadedCourses = await fileManager.LoadSavedCourses(); - var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); - - loadedCourse.Settings.Should().BeEquivalentTo(testCourse.Settings); - } - - - [Fact] - public async Task EmptyCourseModules_CanBeSavedAndLoaded() - { - LocalCourse testCourse = new() - { - Settings = new() { Name = "Test Course with modules" }, - Modules = [ - new() - { - Name = "test module 1", - Assignments = [], - Quizzes = [] - } - ] - }; - - await fileManager.SaveCourseAsync(testCourse, null); - - var loadedCourses = await fileManager.LoadSavedCourses(); - var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); - - loadedCourse.Modules.Should().BeEquivalentTo(testCourse.Modules); - } - - [Fact] - public async Task CourseModules_WithAssignments_CanBeSavedAndLoaded() - { - LocalCourse testCourse = new() - { - Settings = new() { Name = "Test Course with modules and assignments" }, - Modules = [ - new() - { - Name = "test module 1 with assignments", - Assignments = [ - new() - { - Name = "test assignment", - Description = "here is the description", - DueAt = new DateTime(), - LockAt = new DateTime(), - SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], - LocalAssignmentGroupName = "Final Project", - Rubric = [ - new() { Points = 4, Label = "do task 1" }, - new() { Points = 2, Label = "do task 2" }, - ] - } - ], - Quizzes = [] - } - ] - }; - - await fileManager.SaveCourseAsync(testCourse, null); - - var loadedCourses = await fileManager.LoadSavedCourses(); - var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); - - var actualAssignments = loadedCourse.Modules.First().Assignments; - var expectedAssignments = testCourse.Modules.First().Assignments; - actualAssignments.Should().BeEquivalentTo(expectedAssignments); - } - - - [Fact] - public async Task CourseModules_WithQuizzes_CanBeSavedAndLoaded() - { - LocalCourse testCourse = new() - { - Settings = new() { Name = "Test Course with modules and quiz" }, - Modules = [ - new() - { - Name = "test module 1 with quiz", - Assignments = [], - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = "quiz description", - LockAt = new DateTime(2022, 10, 3, 12, 5, 0), - DueAt = new DateTime(2022, 10, 3, 12, 5, 0), - ShuffleAnswers = true, - OneQuestionAtATime = true, - LocalAssignmentGroupName = "Assignments", - Questions = [ - new() - { - Text = "test essay", - QuestionType = QuestionType.ESSAY, - Points = 1 - } - ] - } - ] - } - ] - }; - - await fileManager.SaveCourseAsync(testCourse, null); - - var loadedCourses = await fileManager.LoadSavedCourses(); - var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); - - loadedCourse.Modules.First().Quizzes.Should().BeEquivalentTo(testCourse.Modules.First().Quizzes); - } - - - [Fact] - public async Task MarkdownStorage_FullyPopulated_DoesNotLoseData() - { - LocalCourse testCourse = new() - { - Settings = new() - { - AssignmentGroups = [], - Name = "Test Course with lots of data", - DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday], - StartDate = new DateTime(), - EndDate = new DateTime(), - DefaultDueTime = new() { Hour = 1, Minute = 59 }, - }, - Modules = [ - new() - { - Name = "new test module", - Assignments = [ - new() - { - Name = "test assignment", - Description = "here is the description", - DueAt = new DateTime(), - LockAt = new DateTime(), - SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], - LocalAssignmentGroupName = "Final Project", - Rubric = [ - new() { Points = 4, Label = "do task 1" }, - new() { Points = 2, Label = "do task 2" }, - ] - } - ], - Quizzes = [ - new() - { - Name = "Test Quiz", - Description = "quiz description", - LockAt = new DateTime(), - DueAt = new DateTime(), - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = [ - new() - { - Text = "test short answer", - QuestionType = QuestionType.SHORT_ANSWER, - Points = 1 - } - ] - } - ] - } - ] - }; - - await fileManager.SaveCourseAsync(testCourse, null); - - var loadedCourses = await fileManager.LoadSavedCourses(); - var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); - - loadedCourse.Should().BeEquivalentTo(testCourse); - } - - - [Fact] - public async Task MarkdownStorage_CanPersistPages() - { - LocalCourse testCourse = new() - { - Settings = new() - { - AssignmentGroups = [], - Name = "Test Course with page", - DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday], - StartDate = new DateTime(), - EndDate = new DateTime(), - DefaultDueTime = new() { Hour = 1, Minute = 59 }, - }, - Modules = [ - new() - { - Name = "page test module", - Pages = [ - new() - { - Name = "test page persistence", - DueAt = new DateTime(), - Text = "this is some\n## markdown\n" - } - ] - } - ] - }; - - await fileManager.SaveCourseAsync(testCourse, null); - - var loadedCourses = await fileManager.LoadSavedCourses(); - var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); - - loadedCourse.Should().BeEquivalentTo(testCourse); - } -} diff --git a/Management.Test/Management.Test.csproj b/Management.Test/Management.Test.csproj deleted file mode 100644 index ded8b86..0000000 --- a/Management.Test/Management.Test.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - - net8.0 - enable - enable - - false - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - diff --git a/Management.Test/Markdown/AssignmentMarkdownTests.cs b/Management.Test/Markdown/AssignmentMarkdownTests.cs deleted file mode 100644 index a29a7d6..0000000 --- a/Management.Test/Markdown/AssignmentMarkdownTests.cs +++ /dev/null @@ -1,156 +0,0 @@ -using LocalModels; - -public class AssignmentMarkdownTests -{ - [Fact] - public void TestCanParseAssignmentSettings() - { - var assignment = new LocalAssignment() - { - Name = "test assignment", - Description = "here is the description", - DueAt = new DateTime(), - LockAt = new DateTime(), - SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], - LocalAssignmentGroupName = "Final Project", - Rubric = new List() { - new RubricItem() {Points = 4, Label="do task 1"}, - new RubricItem() {Points = 2, Label="do task 2"}, - } - }; - - var assignmentMarkdown = assignment.ToMarkdown(); - - var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); - parsedAssignment.Should().BeEquivalentTo(assignment); - } - [Fact] - public void AssignmentWithEmptyRubric_CanBeParsed() - { - var assignment = new LocalAssignment() - { - Name = "test assignment", - Description = "here is the description", - DueAt = new DateTime(), - LockAt = new DateTime(), - SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], - LocalAssignmentGroupName = "Final Project", - Rubric = new List() { } - }; - - var assignmentMarkdown = assignment.ToMarkdown(); - - var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); - parsedAssignment.Should().BeEquivalentTo(assignment); - } - [Fact] - public void AssignmentWithEmptySubmissionTypes_CanBeParsed() - { - var assignment = new LocalAssignment() - { - Name = "test assignment", - Description = "here is the description", - DueAt = new DateTime(), - LockAt = new DateTime(), - SubmissionTypes = [], - LocalAssignmentGroupName = "Final Project", - Rubric = new List() { - new RubricItem() {Points = 4, Label="do task 1"}, - new RubricItem() {Points = 2, Label="do task 2"}, - } - }; - - var assignmentMarkdown = assignment.ToMarkdown(); - - var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); - parsedAssignment.Should().BeEquivalentTo(assignment); - } - - [Fact] - public void AssignmentWithoutLockAtDate_CanBeParsed() - { - var assignment = new LocalAssignment() - { - Name = "test assignment", - Description = "here is the description", - DueAt = new DateTime(), - LockAt = null, - SubmissionTypes = [], - LocalAssignmentGroupName = "Final Project", - Rubric = new List() { - new RubricItem() {Points = 4, Label="do task 1"}, - new RubricItem() {Points = 2, Label="do task 2"}, - } - }; - - var assignmentMarkdown = assignment.ToMarkdown(); - - var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); - parsedAssignment.Should().BeEquivalentTo(assignment); - } - - [Fact] - public void AssignmentWithoutDescription_CanBeParsed() - { - var assignment = new LocalAssignment() - { - Name = "test assignment", - Description = "", - DueAt = new DateTime(), - LockAt = new DateTime(), - SubmissionTypes = [], - LocalAssignmentGroupName = "Final Project", - Rubric = new List() { - new RubricItem() {Points = 4, Label="do task 1"}, - new RubricItem() {Points = 2, Label="do task 2"}, - } - }; - - var assignmentMarkdown = assignment.ToMarkdown(); - - var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); - parsedAssignment.Should().BeEquivalentTo(assignment); - } - [Fact] - public void Assignments_CanHaveThreeDashes() - { - var assignment = new LocalAssignment() - { - Name = "test assignment", - Description = "test assignment\n---\nsomestuff", - DueAt = new DateTime(), - LockAt = new DateTime(), - SubmissionTypes = [], - LocalAssignmentGroupName = "Final Project", - Rubric = new List() - { - } - }; - - var assignmentMarkdown = assignment.ToMarkdown(); - - var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); - parsedAssignment.Should().BeEquivalentTo(assignment); - } - [Fact] - public void Assignments_CanRestrictUploadTypes() - { - var assignment = new LocalAssignment() - { - Name = "test assignment", - Description = "here is the description", - DueAt = new DateTime(), - LockAt = new DateTime(), - SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], - AllowedFileUploadExtensions = ["pdf", "txt"], - LocalAssignmentGroupName = "Final Project", - Rubric = new List() {} - }; - - var assignmentMarkdown = assignment.ToMarkdown(); - - var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); - parsedAssignment.Should().BeEquivalentTo(assignment); - } - -} diff --git a/Management.Test/Markdown/PageMarkdownTests.cs b/Management.Test/Markdown/PageMarkdownTests.cs deleted file mode 100644 index 523509e..0000000 --- a/Management.Test/Markdown/PageMarkdownTests.cs +++ /dev/null @@ -1,21 +0,0 @@ -using LocalModels; - -public class PageMarkdownTests -{ - [Fact] - public void TestCanParsePage() - { - var page = new LocalCoursePage - { - Name = "test title", - Text = "test text content", - DueAt = new DateTime() - }; - - var pageMarkdown = page.ToMarkdown(); - - var parsedPage = LocalCoursePage.ParseMarkdown(pageMarkdown); - - parsedPage.Should().BeEquivalentTo(page); - } -} diff --git a/Management.Test/Markdown/Quiz/MatchingTests.cs b/Management.Test/Markdown/Quiz/MatchingTests.cs deleted file mode 100644 index e58b16d..0000000 --- a/Management.Test/Markdown/Quiz/MatchingTests.cs +++ /dev/null @@ -1,159 +0,0 @@ -using LocalModels; - -public class MatchingTests -{ - [Fact] - public void CanParseMatchingQuestion() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: ---- -Match the following terms & definitions - -^ statement - a single command to be executed -^ identifier - name of a variable -^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.) -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - firstQuestion.QuestionType.Should().Be(QuestionType.MATCHING); - firstQuestion.Text.Should().NotContain("statement"); - firstQuestion.Answers.First().MatchedText.Should().Be("a single command to be executed"); - } - - [Fact] - public void CanCreateMarkdownForMatchingQuesiton() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: ---- -Match the following terms & definitions - -^ statement - a single command to be executed -^ identifier - name of a variable -^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.) -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var questionMarkdown = quiz.Questions.First().ToMarkdown(); - var expectedMarkdown = @"Points: 1 -Match the following terms & definitions - -^ statement - a single command to be executed -^ identifier - name of a variable -^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)"; - questionMarkdown.Should().Contain(expectedMarkdown); - } - - [Fact] - public void WhitespaceIsOptional() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: ---- -Match the following terms & definitions - -^statement - a single command to be executed -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - quiz.Questions.First().Answers.First().Text.Should().Be("statement"); - } - [Fact] - public void CanHaveDistractors() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: ---- -Match the following terms & definitions - -^statement - a single command to be executed -^ - this is the distractor -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - quiz.Questions.First().MatchDistractors.Should().BeEquivalentTo(["this is the distractor"]); - } - [Fact] - public void CanHaveDistractorsAndBePersisted() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: ---- -Match the following terms & definitions - -^ statement - a single command to be executed -^ - this is the distractor -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var quizMarkdown = quiz.ToMarkdown(); - - quizMarkdown.Should().Contain("^ statement - a single command to be executed\n^ - this is the distractor"); - } - [Fact] - public void DistractorsDoNotAddDelimiterOntheEnd() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: ---- - -Points: 2 - -Match up the term with the best possible answer. -^ - a variable name -^ - A reserved word with special meaning to the compiler -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var quizMarkdown = quiz.ToMarkdown(); - - quizMarkdown.Should().Contain(@"Match up the term with the best possible answer. - -^ - a variable name -^ - A reserved word with special meaning to the compiler"); - } -} diff --git a/Management.Test/Markdown/Quiz/MultipleAnswersTests.cs b/Management.Test/Markdown/Quiz/MultipleAnswersTests.cs deleted file mode 100644 index e297ade..0000000 --- a/Management.Test/Markdown/Quiz/MultipleAnswersTests.cs +++ /dev/null @@ -1,125 +0,0 @@ -using LocalModels; - -public class MultipleAnswersTests -{ - - [Fact] - public void QuzMarkdownIncludesMultipleAnswerQuestion() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = "desc", - LockAt = DateTime.MaxValue, - DueAt = DateTime.MaxValue, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = new LocalQuizQuestion[] - { - new() - { - Text = "oneline question", - Points = 1, - QuestionType = QuestionType.MULTIPLE_ANSWERS, - Answers = new LocalQuizQuestionAnswer[] - { - new() { Correct = true, Text = "true" }, - new() { Correct = true, Text = "false"}, - new() { Correct = false, Text = "neither"}, - } - } - } - }; - var markdown = quiz.ToMarkdown(); - var expectedQuestionString = @" -Points: 1 -oneline question -[*] true -[*] false -[ ] neither -"; - markdown.Should().Contain(expectedQuestionString); - } - - [Fact] - public void CanParseQuestionWithMultipleAnswers() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Which events are triggered when the user clicks on an input field? -[*] click -[*] focus -[*] mousedown -[] submit -[] change -[] mouseout -[] keydown ---- -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - firstQuestion.Points.Should().Be(1); - firstQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_ANSWERS); - firstQuestion.Text.Should().Contain("Which events are triggered when the user clicks on an input field?"); - firstQuestion.Answers.First().Text.Should().Be("click"); - firstQuestion.Answers.First().Correct.Should().BeTrue(); - firstQuestion.Answers.ElementAt(3).Correct.Should().BeFalse(); - firstQuestion.Answers.ElementAt(3).Text.Should().Be("submit"); - } - - - [Fact] - public void CanUseBracesInAnswerFormultipleAnswer() - { - var rawMarkdownQuestion = @" -Which events are triggered when the user clicks on an input field? -[*] `int[] theThing()` -[] keydown -"; - - var question = LocalQuizQuestion.ParseMarkdown(rawMarkdownQuestion, 0); - question.Answers.First().Text.Should().Be("`int[] theThing()`"); - question.Answers.Count().Should().Be(2); - } - - [Fact] - public void CanUseBracesInAnswerFormultipleAnswer_MultiLine() - { - var rawMarkdownQuestion = @" -Which events are triggered when the user clicks on an input field? -[*] -``` -int[] myNumbers = new int[] { }; -DoSomething(ref myNumbers); -static void DoSomething(ref int[] numbers) -{ - // do something -} -``` -"; - - var question = LocalQuizQuestion.ParseMarkdown(rawMarkdownQuestion, 0); - question.Answers.First().Text.Should().Be(@"``` -int[] myNumbers = new int[] { }; -DoSomething(ref myNumbers); -static void DoSomething(ref int[] numbers) -{ - // do something -} -```"); - question.Answers.Count().Should().Be(1); - } -} diff --git a/Management.Test/Markdown/Quiz/MultipleChoiceTests.cs b/Management.Test/Markdown/Quiz/MultipleChoiceTests.cs deleted file mode 100644 index 39d366f..0000000 --- a/Management.Test/Markdown/Quiz/MultipleChoiceTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using LocalModels; - -public class MultipleChoiceTests -{ - [Fact] - public void QuzMarkdownIncludesMultipleChoiceQuestion() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = "desc", - LockAt = DateTime.MaxValue, - DueAt = DateTime.MaxValue, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = new LocalQuizQuestion[] - { - new LocalQuizQuestion() - { - Points = 2, - Text = @"`some type` of question - -with many - -``` -lines -``` -", - QuestionType = QuestionType.MULTIPLE_CHOICE, - Answers = new LocalQuizQuestionAnswer[] - { - new LocalQuizQuestionAnswer() { Correct = true, Text = "true" }, - new LocalQuizQuestionAnswer() { Correct = false, Text = "false\n\nendline" }, - } - } - } - }; - - var markdown = quiz.ToMarkdown(); - var expectedQuestionString = @" -Points: 2 -`some type` of question - -with many - -``` -lines -``` - -*a) true -b) false - -endline -"; - markdown.Should().Contain(expectedQuestionString); - } - - - [Fact] - public void LetterOptionalForMultipleChoice() - { - - var questionMarkdown = @"Points: 2 -`some type` of question -*) true -) false - "; - var question = LocalQuizQuestion.ParseMarkdown(questionMarkdown, 0); - question.Answers.Count().Should().Be(2); - } -} diff --git a/Management.Test/Markdown/Quiz/QuizDeterministicChecks.cs b/Management.Test/Markdown/Quiz/QuizDeterministicChecks.cs deleted file mode 100644 index 9348110..0000000 --- a/Management.Test/Markdown/Quiz/QuizDeterministicChecks.cs +++ /dev/null @@ -1,218 +0,0 @@ -using System.Text; -using LocalModels; - -public class QuizDeterministicChecks -{ - - [Fact] - public void SerializationIsDeterministic_EmptyQuiz() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = "quiz description", - LockAt = new DateTime(2022, 10, 3, 12, 5, 0), - DueAt = new DateTime(2022, 10, 3, 12, 5, 0), - ShuffleAnswers = true, - OneQuestionAtATime = true, - LocalAssignmentGroupName = "Assignments" - }; - var quizMarkdown = quiz.ToMarkdown(); - - var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown); - parsedQuiz.Should().BeEquivalentTo(quiz); - } - - [Fact] - public void SerializationIsDeterministic_ShowCorrectAnswers() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = "quiz description", - LockAt = new DateTime(2022, 10, 3, 12, 5, 0), - DueAt = new DateTime(2022, 10, 3, 12, 5, 0), - showCorrectAnswers = false, - ShuffleAnswers = true, - OneQuestionAtATime = true, - LocalAssignmentGroupName = "Assignments" - }; - var quizMarkdown = quiz.ToMarkdown(); - - var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown); - parsedQuiz.Should().BeEquivalentTo(quiz); - } - - [Fact] - public void SerializationIsDeterministic_ShortAnswer() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = "quiz description", - LockAt = new DateTime(2022, 10, 3, 12, 5, 0), - DueAt = new DateTime(2022, 10, 3, 12, 5, 0), - ShuffleAnswers = true, - OneQuestionAtATime = true, - LocalAssignmentGroupName = "Assignments", - Questions = new LocalQuizQuestion[] - { - new () - { - Text = "test short answer", - QuestionType = QuestionType.SHORT_ANSWER, - Points = 1 - } - } - }; - var quizMarkdown = quiz.ToMarkdown(); - - var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown); - parsedQuiz.Should().BeEquivalentTo(quiz); - } - - [Fact] - public void SerializationIsDeterministic_Essay() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = "quiz description", - LockAt = new DateTime(2022, 10, 3, 12, 5, 0), - DueAt = new DateTime(2022, 10, 3, 12, 5, 0), - ShuffleAnswers = true, - OneQuestionAtATime = true, - LocalAssignmentGroupName = "Assignments", - Questions = new LocalQuizQuestion[] - { - new () - { - Text = "test essay", - QuestionType = QuestionType.ESSAY, - Points = 1 - } - } - }; - var quizMarkdown = quiz.ToMarkdown(); - - var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown); - parsedQuiz.Should().BeEquivalentTo(quiz); - } - - [Fact] - public void SerializationIsDeterministic_MultipleAnswer() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = "quiz description", - LockAt = new DateTime(2022, 10, 3, 12, 5, 0), - DueAt = new DateTime(2022, 10, 3, 12, 5, 0), - ShuffleAnswers = true, - OneQuestionAtATime = true, - LocalAssignmentGroupName = "Assignments", - Questions = new LocalQuizQuestion[] - { - new () - { - Text = "test multiple answer", - QuestionType = QuestionType.MULTIPLE_ANSWERS, - Points = 1, - Answers = new LocalQuizQuestionAnswer[] - { - new() { - Correct = true, - Text="yes", - }, - new() { - Correct = true, - Text="no", - } - } - } - } - }; - var quizMarkdown = quiz.ToMarkdown(); - - var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown); - parsedQuiz.Should().BeEquivalentTo(quiz); - } - - [Fact] - public void SerializationIsDeterministic_MultipleChoice() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = "quiz description", - LockAt = new DateTime(2022, 10, 3, 12, 5, 0), - DueAt = new DateTime(2022, 10, 3, 12, 5, 0), - ShuffleAnswers = true, - OneQuestionAtATime = true, - LocalAssignmentGroupName = "Assignments", - Questions = new LocalQuizQuestion[] - { - new () - { - Text = "test multiple choice", - QuestionType = QuestionType.MULTIPLE_CHOICE, - Points = 1, - Answers = new LocalQuizQuestionAnswer[] - { - new() { - Correct = true, - Text="yes", - }, - new() { - Correct = false, - Text="no", - } - } - } - } - }; - var quizMarkdown = quiz.ToMarkdown(); - - var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown); - parsedQuiz.Should().BeEquivalentTo(quiz); - } - [Fact] - public void SerializationIsDeterministic_Matching() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = "quiz description", - LockAt = new DateTime(2022, 10, 3, 12, 5, 0), - DueAt = new DateTime(2022, 10, 3, 12, 5, 0), - ShuffleAnswers = true, - OneQuestionAtATime = true, - LocalAssignmentGroupName = "Assignments", - Questions = new LocalQuizQuestion[] - { - new () - { - Text = "test matching", - QuestionType = QuestionType.MATCHING, - Points = 1, - Answers = [ - new() { - Correct = true, - Text="yes", - MatchedText = "testing yes" - }, - new() { - Correct = true, - Text="no", - MatchedText = "testing no" - } - ] - } - } - }; - var quizMarkdown = quiz.ToMarkdown(); - - var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown); - parsedQuiz.Should().BeEquivalentTo(quiz); - } -} diff --git a/Management.Test/Markdown/Quiz/QuizMarkdownTests.cs b/Management.Test/Markdown/Quiz/QuizMarkdownTests.cs deleted file mode 100644 index 9f1116e..0000000 --- a/Management.Test/Markdown/Quiz/QuizMarkdownTests.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System.Text; -using LocalModels; - -// try to follow syntax from https://github.com/gpoore/text2qti -public class QuizMarkdownTests -{ - [Fact] - public void CanSerializeQuizToMarkdown() - { - var quiz = new LocalQuiz() - { - Name = "Test Quiz", - Description = @" -# quiz description - -this is my description in markdown - -`here is code` -", - LockAt = DateTime.MaxValue, - DueAt = DateTime.MaxValue, - ShuffleAnswers = true, - OneQuestionAtATime = false, - LocalAssignmentGroupName = "someId", - AllowedAttempts = -1, - Questions = [] - }; - - var markdown = quiz.ToMarkdown(); - - markdown.Should().Contain("Name: Test Quiz"); - markdown.Should().Contain(quiz.Description); - markdown.Should().Contain("ShuffleAnswers: true"); - markdown.Should().Contain("OneQuestionAtATime: false"); - markdown.Should().Contain("AssignmentGroup: someId"); - markdown.Should().Contain("AllowedAttempts: -1"); - } - - - [Fact] - public void TestCanParseMarkdownQuizWithNoQuestions() - { - var rawMarkdownQuiz = new StringBuilder(); - rawMarkdownQuiz.Append("Name: Test Quiz\n"); - rawMarkdownQuiz.Append("ShuffleAnswers: true\n"); - rawMarkdownQuiz.Append("OneQuestionAtATime: false\n"); - rawMarkdownQuiz.Append("DueAt: 2023-08-21T23:59:00\n"); - rawMarkdownQuiz.Append("LockAt: 2023-08-21T23:59:00\n"); - rawMarkdownQuiz.Append("AssignmentGroup: Assignments\n"); - rawMarkdownQuiz.Append("AllowedAttempts: -1\n"); - rawMarkdownQuiz.Append("Description: this is the\n"); - rawMarkdownQuiz.Append("multi line\n"); - rawMarkdownQuiz.Append("description\n"); - rawMarkdownQuiz.Append("---\n"); - rawMarkdownQuiz.Append('\n'); - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz.ToString()); - - - var expectedDescription = new StringBuilder(); - expectedDescription.Append("this is the\n"); - expectedDescription.Append("multi line\n"); - expectedDescription.Append("description"); - - quiz.Name.Should().Be("Test Quiz"); - quiz.ShuffleAnswers.Should().Be(true); - quiz.OneQuestionAtATime.Should().BeFalse(); - quiz.AllowedAttempts.Should().Be(-1); - quiz.Description.Should().Be(expectedDescription.ToString()); - } - [Fact] - public void TestCanParseMarkdownQuizPassword() - { - - var password = "this-is-the-password"; - var rawMarkdownQuiz = new StringBuilder(); - rawMarkdownQuiz.Append("Name: Test Quiz\n"); - rawMarkdownQuiz.Append($"Password: {password}\n"); - rawMarkdownQuiz.Append("ShuffleAnswers: true\n"); - rawMarkdownQuiz.Append("OneQuestionAtATime: false\n"); - rawMarkdownQuiz.Append("DueAt: 2023-08-21T23:59:00\n"); - rawMarkdownQuiz.Append("LockAt: 2023-08-21T23:59:00\n"); - rawMarkdownQuiz.Append("AssignmentGroup: Assignments\n"); - rawMarkdownQuiz.Append("AllowedAttempts: -1\n"); - rawMarkdownQuiz.Append("Description: this is the\n"); - rawMarkdownQuiz.Append("multi line\n"); - rawMarkdownQuiz.Append("description\n"); - rawMarkdownQuiz.Append("---\n"); - rawMarkdownQuiz.Append('\n'); - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz.ToString()); - - - quiz.Password.Should().Be(password); - } - - [Fact] - public void TestCanParseMarkdownQuiz_CanConfigureToShowCorrectAnswers() - { - var rawMarkdownQuiz = new StringBuilder(); - rawMarkdownQuiz.Append("Name: Test Quiz\n"); - rawMarkdownQuiz.Append("ShuffleAnswers: true\n"); - rawMarkdownQuiz.Append("OneQuestionAtATime: false\n"); - rawMarkdownQuiz.Append("ShowCorrectAnswers: false\n"); - rawMarkdownQuiz.Append("DueAt: 2023-08-21T23:59:00\n"); - rawMarkdownQuiz.Append("LockAt: 2023-08-21T23:59:00\n"); - rawMarkdownQuiz.Append("AssignmentGroup: Assignments\n"); - rawMarkdownQuiz.Append("AllowedAttempts: -1\n"); - rawMarkdownQuiz.Append("Description: this is the\n"); - rawMarkdownQuiz.Append("multi line\n"); - rawMarkdownQuiz.Append("description\n"); - rawMarkdownQuiz.Append("---\n"); - rawMarkdownQuiz.Append('\n'); - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz.ToString()); - - - quiz.showCorrectAnswers.Should().BeFalse(); - } - - [Fact] - public void TestCanParseQuizWithQuestions() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Points: 2 -`some type` of question - -with many - -``` -lines -``` - -*a) true -b) false - - endline"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - firstQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_CHOICE); - firstQuestion.Points.Should().Be(2); - firstQuestion.Text.Should().Contain("```"); - firstQuestion.Text.Should().Contain("`some type` of question"); - firstQuestion.Answers.First().Text.Should().Be("true"); - firstQuestion.Answers.First().Correct.Should().BeTrue(); - firstQuestion.Answers.ElementAt(1).Correct.Should().BeFalse(); - firstQuestion.Answers.ElementAt(1).Text.Should().Contain("endline"); - } - - [Fact] - public void CanParseMultipleQuestions() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Which events are triggered when the user clicks on an input field? -[*] click ---- -points: 2 -`some type` of question -*a) true -b) false -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - firstQuestion.Points.Should().Be(1); - firstQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_ANSWERS); - var secondQuestion = quiz.Questions.ElementAt(1); - secondQuestion.Points.Should().Be(2); - secondQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_CHOICE); - } - - [Fact] - public void ShortAnswerToMarkdown_IsCorrect() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Which events are triggered when the user clicks on an input field? -short answer -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - - var questionMarkdown = firstQuestion.ToMarkdown(); - var expectedMarkdown = @"Points: 1 -Which events are triggered when the user clicks on an input field? -short_answer"; - questionMarkdown.Should().Contain(expectedMarkdown); - } - [Fact] - public void NegativePoints_IsAllowed() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Points: -4 -Which events are triggered when the user clicks on an input field? -short answer -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - firstQuestion.Points.Should().Be(-4); - } - [Fact] - public void FloatingPointPoints_IsAllowed() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Points: 4.56 -Which events are triggered when the user clicks on an input field? -short answer -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - firstQuestion.Points.Should().Be(4.56); - } -} diff --git a/Management.Test/Markdown/Quiz/TextAnswerTests.cs b/Management.Test/Markdown/Quiz/TextAnswerTests.cs deleted file mode 100644 index f4dbe69..0000000 --- a/Management.Test/Markdown/Quiz/TextAnswerTests.cs +++ /dev/null @@ -1,114 +0,0 @@ -using LocalModels; - -public class TextAnswerTests -{ - [Fact] - public void CanParseEssay() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Which events are triggered when the user clicks on an input field? -essay -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - firstQuestion.Points.Should().Be(1); - firstQuestion.QuestionType.Should().Be(QuestionType.ESSAY); - firstQuestion.Text.Should().NotContain("essay"); - } - - [Fact] - public void CanParseShortAnswer() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Which events are triggered when the user clicks on an input field? -short answer -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - firstQuestion.Points.Should().Be(1); - firstQuestion.QuestionType.Should().Be(QuestionType.SHORT_ANSWER); - firstQuestion.Text.Should().NotContain("short answer"); - } - - [Fact] - public void ShortAnswerToMarkdown_IsCorrect() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Which events are triggered when the user clicks on an input field? -short answer -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - - var questionMarkdown = firstQuestion.ToMarkdown(); - var expectedMarkdown = @"Points: 1 -Which events are triggered when the user clicks on an input field? -short_answer"; - questionMarkdown.Should().Contain(expectedMarkdown); - } - - [Fact] - public void EssayQuestionToMarkdown_IsCorrect() - { - var rawMarkdownQuiz = @" -Name: Test Quiz -ShuffleAnswers: true -OneQuestionAtATime: false -DueAt: 2023-08-21T23:59:00 -LockAt: 2023-08-21T23:59:00 -AssignmentGroup: Assignments -AllowedAttempts: -1 -Description: this is the -multi line -description ---- -Which events are triggered when the user clicks on an input field? -essay -"; - - var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); - var firstQuestion = quiz.Questions.First(); - - var questionMarkdown = firstQuestion.ToMarkdown(); - var expectedMarkdown = @"Points: 1 -Which events are triggered when the user clicks on an input field? -essay"; - questionMarkdown.Should().Contain(expectedMarkdown); - } -} diff --git a/Management.Test/Markdown/RubricMarkdownTests.cs b/Management.Test/Markdown/RubricMarkdownTests.cs deleted file mode 100644 index 23b0aa4..0000000 --- a/Management.Test/Markdown/RubricMarkdownTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -using LocalModels; - -public class RubricMarkdownTests -{ - - [Fact] - public void TestCanParseOneItem() - { - var rawRubric = @" - - 2pts: this is the task - "; - - var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); - rubric.Count().Should().Be(1); - rubric.First().IsExtraCredit.Should().BeFalse(); - rubric.First().Label.Should().Be("this is the task"); - rubric.First().Points.Should().Be(2); - } - - [Fact] - public void TestCanParseMultipleItems() - { - var rawRubric = @" - - 2pts: this is the task - - 3pts: this is the other task - "; - - var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); - rubric.Count().Should().Be(2); - rubric.ElementAt(1).IsExtraCredit.Should().BeFalse(); - rubric.ElementAt(1).Label.Should().Be("this is the other task"); - rubric.ElementAt(1).Points.Should().Be(3); - } - - [Fact] - public void TestCanParseSinglePoint() - { - var rawRubric = @" - - 1pt: this is the task - "; - - var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); - rubric.First().IsExtraCredit.Should().BeFalse(); - rubric.First().Label.Should().Be("this is the task"); - rubric.First().Points.Should().Be(1); - } - - [Fact] - public void TestCanParseSingleExtraCredit_LowerCase() - { - var rawRubric = @" - - 1pt: (extra credit) this is the task - "; - - var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); - rubric.First().IsExtraCredit.Should().BeTrue(); - rubric.First().Label.Should().Be("(extra credit) this is the task"); - } - - [Fact] - public void TestCanParseSingleExtraCredit_UpperCase() - { - var rawRubric = @" - - 1pt: (Extra Credit) this is the task - "; - - var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); - rubric.First().IsExtraCredit.Should().BeTrue(); - rubric.First().Label.Should().Be("(Extra Credit) this is the task"); - } - - [Fact] - public void TestCanParseFloatingPointNubmers() - { - var rawRubric = @" - - 1.5pt: this is the task - "; - - var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); - rubric.First().Points.Should().Be(1.5); - } - [Fact] - public void TestCanParseNegativeNubmers() - { - var rawRubric = @" - - -2pt: this is the task - "; - - var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); - rubric.First().Points.Should().Be(-2.0); - } - [Fact] - public void TestCanParseNegativeFloatingPointNubmers() - { - var rawRubric = @" - - -2895.00053pt: this is the task - "; - - var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); - rubric.First().Points.Should().Be(-2895.00053); - } -} diff --git a/Management.Test/Services/CanvasServiceTests.cs b/Management.Test/Services/CanvasServiceTests.cs deleted file mode 100644 index be2a0aa..0000000 --- a/Management.Test/Services/CanvasServiceTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// using CanvasModel.Courses; -// using CanvasModel.EnrollmentTerms; -// using FluentAssertions; -// using Moq; -// using RestSharp; -// using System.Net; - -// namespace Management.Test; - -// public class ICanvasServiceTests -// { -// [Fact] -// public async Task CanReadCanvasSemesters() -// { -// var expectedTerms = new EnrollmentTermModel[] { -// new EnrollmentTermModel( -// Id: 1, -// Name: "one", -// StartAt: new DateTime(2022, 1, 1), -// EndAt: new DateTime(2022, 2, 1) -// ), -// }; -// Mock mockRequestor = getTermsMock(expectedTerms); -// var service = new ICanvasService(mockRequestor.Object); -// var canvasTerms = await service.GetTerms(); -// canvasTerms.Should().BeEquivalentTo(expectedTerms); -// } -// [Fact] -// public async Task CanGetActiveTerms() -// { -// var expectedTerms = new EnrollmentTermModel[] { -// new EnrollmentTermModel( -// Id: 1, -// Name: "one", -// StartAt: new DateTime(2022, 5, 1), -// EndAt: new DateTime(2022, 7, 1) -// ), -// new EnrollmentTermModel( -// Id: 2, -// Name: "two", -// StartAt: new DateTime(2022, 7, 1), -// EndAt: new DateTime(2022, 9, 1) -// ), -// new EnrollmentTermModel( -// Id: 3, -// Name: "three", -// StartAt: new DateTime(2022, 9, 1), -// EndAt: new DateTime(2022, 10, 1) -// ), -// new EnrollmentTermModel( -// Id: 4, -// Name: "four", -// StartAt: new DateTime(2022, 10, 1), -// EndAt: new DateTime(2022, 11, 1) -// ), -// }; -// Mock mockRequestor = getTermsMock(expectedTerms); -// var service = new ICanvasService(mockRequestor.Object); - -// var queryDate = new DateTime(2022, 6, 1); -// var canvasTerms = await service.GetCurrentTermsFor(queryDate); - -// canvasTerms.Count().Should().Be(3); - -// var termIds = canvasTerms.Select(t => t.Id); -// var expectedIds = new int[] { 1, 2, 3 }; -// termIds.Should().BeEquivalentTo(expectedIds); -// } - -// private static Mock getTermsMock(EnrollmentTermModel[] expectedTerms) -// { -// var data = new RedundantEnrollmentTermsResponse(EnrollmentTerms: expectedTerms); -// var response = new RestResponse(); -// response.Data = data; - -// var mockRequestor = new Mock(); -// mockRequestor -// .Setup(s => s.GetAsync(It.IsAny())) -// .ReturnsAsync(response); -// return mockRequestor; -// } -// } diff --git a/Management.Test/Usings.cs b/Management.Test/Usings.cs deleted file mode 100644 index 600fb6d..0000000 --- a/Management.Test/Usings.cs +++ /dev/null @@ -1,3 +0,0 @@ -global using System.Text.Json; -global using FluentAssertions; -global using Xunit; diff --git a/Management.Test/ViewModels/MonthDetailTests.cs b/Management.Test/ViewModels/MonthDetailTests.cs deleted file mode 100644 index 4da2ff8..0000000 --- a/Management.Test/ViewModels/MonthDetailTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Management.Web.Pages.Course.CourseCalendar; - -public class MonthDetailTests -{ - [Fact] - public void TestCanGetMonthName() - { - var calendarMonth = new CalendarMonth(2022, 2); - -#pragma warning disable BL0005 // Component parameter should not be set outside of its component. - var detail = new MonthDetail() - { - Month = calendarMonth - }; -#pragma warning restore BL0005 // Component parameter should not be set outside of its component. - - - detail.MonthName.Should().Be("February"); - } -} diff --git a/Management.Web/App.razor b/Management.Web/App.razor deleted file mode 100644 index 623580d..0000000 --- a/Management.Web/App.razor +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - Not found - -

Sorry, there's nothing at this address.

-
-
-
diff --git a/Management.Web/ConfigurationSetup.cs b/Management.Web/ConfigurationSetup.cs deleted file mode 100644 index d8d6dc5..0000000 --- a/Management.Web/ConfigurationSetup.cs +++ /dev/null @@ -1,14 +0,0 @@ -public static class ConfigurationSetup -{ - public static void Canvas(WebApplicationBuilder builder) - { - var canvas_token = builder.Configuration["CANVAS_TOKEN"] ?? throw new Exception("CANVAS_TOKEN is null"); - - var canvas_url = builder.Configuration["CANVAS_URL"]; - if (canvas_url == null) - { - Console.WriteLine("CANVAS_URL is null, defaulting to https://snow.instructure.com"); - builder.Configuration["CANVAS_URL"] = "https://snow.instructure.com"; - } - } -} diff --git a/Management.Web/CustomConsoleExporter.cs b/Management.Web/CustomConsoleExporter.cs deleted file mode 100644 index 8bd6cde..0000000 --- a/Management.Web/CustomConsoleExporter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Diagnostics; -using OpenTelemetry; - -public class CustomConsoleExporter : BaseExporter -{ - public override ExportResult Export(in Batch batch) - { - using var scope = SuppressInstrumentationScope.Begin(); - - foreach (var activity in batch) - { - string[] ignoreOperations = [ - "Microsoft.AspNetCore.Hosting.HttpRequestIn", - ]; - if (!ignoreOperations.Contains(activity.OperationName)) - Console.WriteLine($"{activity.OperationName}: {activity.DisplayName}"); - } - return ExportResult.Success; - } -} diff --git a/Management.Web/Management.Web.csproj b/Management.Web/Management.Web.csproj deleted file mode 100644 index eb791dd..0000000 --- a/Management.Web/Management.Web.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - net8.0 - enable - enable - 6dc43700-9593-43ca-bda7-4fa2c4e7abc7 - - diff --git a/Management.Web/Management.Web.csproj.user b/Management.Web/Management.Web.csproj.user deleted file mode 100644 index 9ff5820..0000000 --- a/Management.Web/Management.Web.csproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - https - - \ No newline at end of file diff --git a/Management.Web/Pages/AssignmentForm/AssignmentForm.razor b/Management.Web/Pages/AssignmentForm/AssignmentForm.razor deleted file mode 100644 index 1733de2..0000000 --- a/Management.Web/Pages/AssignmentForm/AssignmentForm.razor +++ /dev/null @@ -1,229 +0,0 @@ -@using Management.Web.Shared.Components -@using Management.Web.Shared.Components.Forms -@using CanvasModel.Assignments - -@inject CoursePlanner planner -@inject ICanvasService canvas -@inject NavigationManager Navigation -@inject AssignmentEditorContext assignmentContext - -@code { - protected override void OnInitialized() - { - assignmentContext.StateHasChanged += reload; - reload(); - } - private void reload() - { - if (assignmentContext.Assignment != null) - { - name = assignmentContext.Assignment.Name; - } - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - assignmentContext.StateHasChanged -= reload; - } - - private void OnHide() - { - assignmentContext.Assignment = null; - name = ""; - } - private string name { get; set; } = String.Empty; - private bool addingAssignmentToCanvas = false; - private bool deletingAssignmentFromCanvas = false; - private bool showHelp = false; - - private void toggleHelp() => showHelp = !showHelp; - - private void submitHandler() - { - if (assignmentContext.Assignment != null) - { - var newAssignment = assignmentContext.Assignment with - { - Name = name, - }; - - assignmentContext.SaveAssignment(newAssignment); - } - assignmentContext.Assignment = null; - } - - private async Task HandleDelete() - { - if (planner.LocalCourse != null && assignmentContext.Assignment != null) - { - var assignment = assignmentContext.Assignment; - - var currentModule = planner - .LocalCourse - .Modules - .First(m => - m.Assignments.Contains(assignment) - ) ?? throw new Exception("handling assignment delete, could not find module"); - - var newModules = planner.LocalCourse.Modules.Select(m => - m.Name == currentModule.Name - ? m with - { - Assignments = m.Assignments.Where(a => a != assignment).ToArray() - } - : m - ) - .ToArray(); - - planner.LocalCourse = planner.LocalCourse with - { - Modules = newModules - }; - - if (assignmentInCanvas != null && planner.LocalCourse.Settings.CanvasId != null) - { - ulong courseId = planner.LocalCourse.Settings.CanvasId ?? throw new Exception("cannot delete if no course id"); - await canvas.Assignments.Delete(courseId, assignmentInCanvas.Id, assignment.Name); - } - Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name); - } - } - - private void handleNameChange(ChangeEventArgs e) - { - if (assignmentContext.Assignment != null) - { - var newAssignment = assignmentContext.Assignment with { Name = e.Value?.ToString() ?? "" }; - assignmentContext.SaveAssignment(newAssignment); - } - } - - private void setAssignmentGroup(LocalAssignmentGroup? group) - { - if (assignmentContext.Assignment == null) - return; - - var newAssignment = assignmentContext.Assignment with - { - LocalAssignmentGroupName = group?.Name - }; - - assignmentContext.SaveAssignment(newAssignment); - } - - private LocalAssignmentGroup? selectedAssignmentGroup => - planner - .LocalCourse? - .Settings - .AssignmentGroups - .FirstOrDefault(g => g.Name == assignmentContext.Assignment?.LocalAssignmentGroupName); - - private async Task addToCanvas() - { - addingAssignmentToCanvas = true; - await assignmentContext.AddAssignmentToCanvas(); - await planner.LoadCanvasData(); - addingAssignmentToCanvas = false; - } - private async Task updateInCanvas() - { - if(assignmentInCanvas != null) - { - addingAssignmentToCanvas = true; - await assignmentContext.UpdateInCanvas(assignmentInCanvas.Id); - await planner.LoadCanvasData(); - addingAssignmentToCanvas = false; - } - } - - private CanvasAssignment? assignmentInCanvas => - planner.CanvasData?.Assignments.FirstOrDefault(a => a.Name == assignmentContext.Assignment?.Name); - - private string canvasAssignmentUrl => - $"https://snow.instructure.com/courses/{planner.LocalCourse?.Settings.CanvasId}/assignments/{assignmentInCanvas?.Id}"; - - private async Task deleteFromCanvas() - { - if (assignmentInCanvas == null - || planner?.LocalCourse?.Settings.CanvasId == null - || assignmentContext.Assignment == null - ) - return; - - deletingAssignmentFromCanvas = true; - await canvas.Assignments.Delete( - (ulong)planner.LocalCourse.Settings.CanvasId, - assignmentInCanvas.Id, - assignmentContext.Assignment.Name - ); - await planner.LoadCanvasData(); - deletingAssignmentFromCanvas = false; - StateHasChanged(); - } -} - -
-
- @assignmentContext.Assignment?.Name -
- -
- @if (assignmentContext.Assignment != null) - { - - } -
- -
- @if (addingAssignmentToCanvas || deletingAssignmentFromCanvas) - { -
- -
- } - - - - - @if (assignmentInCanvas != null) - { - - View in Canvas - - - - } - -
- - -
diff --git a/Management.Web/Pages/AssignmentForm/AssignmentFormPage.razor b/Management.Web/Pages/AssignmentForm/AssignmentFormPage.razor deleted file mode 100644 index f934fad..0000000 --- a/Management.Web/Pages/AssignmentForm/AssignmentFormPage.razor +++ /dev/null @@ -1,67 +0,0 @@ -@page "/course/{CourseName}/assignment/{AssignmentName}" - -@using CanvasModel.EnrollmentTerms -@using CanvasModel.Courses -@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@using LocalModels -@using Management.Web.Pages.Course.Module.ModuleItems -@using Management.Web.Shared.Components - -@inject IFileStorageManager fileStorageManager -@inject ICanvasService canvas -@inject CoursePlanner planner -@inject AssignmentEditorContext assignmentContext -@inject ILogger logger - -@code { - [Parameter] - public string? CourseName { get; set; } = default!; - [Parameter] - public string? AssignmentName { get; set; } = default!; - - private bool loading { get; set; } = true; - - protected override async Task OnInitializedAsync() - { - if (loading) - { - loading = false; - logger.LogInformation($"loading assignment {CourseName} {AssignmentName}"); - if (planner.LocalCourse == null) - { - var courses = await fileStorageManager.LoadSavedCourses(); - planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName); - logger.LogInformation($"set course to '{planner.LocalCourse?.Settings.Name}'"); - } - - if (assignmentContext.Assignment == null) - { - var assignment = planner - .LocalCourse? - .Modules - .SelectMany(m => m.Assignments) - .FirstOrDefault(a => a.Name == AssignmentName); - - assignmentContext.Assignment = assignment; - logger.LogInformation($"set assignment to '{assignmentContext.Assignment?.Name}'"); - } - await planner.LoadCanvasData(); - base.OnInitialized(); - StateHasChanged(); - } - } -} - -@CourseName - @AssignmentName - -
- @if (loading) - { - - } - - @if (planner.LocalCourse != null && assignmentContext.Assignment != null) - { - - } -
diff --git a/Management.Web/Pages/AssignmentForm/AssignmentMarkdownEditor.razor b/Management.Web/Pages/AssignmentForm/AssignmentMarkdownEditor.razor deleted file mode 100644 index 52a27a9..0000000 --- a/Management.Web/Pages/AssignmentForm/AssignmentMarkdownEditor.razor +++ /dev/null @@ -1,130 +0,0 @@ - -@using Management.Web.Shared.Components - -@inject CoursePlanner planner -@inject AssignmentEditorContext assignmentContext - -@code -{ - [Parameter, EditorRequired] - public bool ShowHelp { get; set; } = false; - protected override void OnInitialized() - { - assignmentContext.StateHasChanged += reload; - reload(); - } - private void reload() - { - if (assignmentContext.Assignment != null) - { - if(rawText == string.Empty) - { - rawText = assignmentContext.Assignment.ToMarkdown(); - this.InvokeAsync(this.StateHasChanged); - } - } - } - public void Dispose() - { - assignmentContext.StateHasChanged -= reload; - } - - private string rawText { get; set; } = string.Empty; - private string? error = null; - - private void handleChange(string newRawAssignment) - { - rawText = newRawAssignment; - if (newRawAssignment != string.Empty) - { - try - { - var parsed = LocalAssignment.ParseMarkdown(newRawAssignment); - error = null; - assignmentContext.SaveAssignment(parsed); - } - catch(AssignmentMarkdownParseException e) - { - error = e.Message; - } - catch(RubricMarkdownParseException e) - { - error = e.Message; - } - finally - { - StateHasChanged(); - } - } - StateHasChanged(); - } - - private MarkupString preview { get - { - return (MarkupString)MarkdownService.Render(assignmentContext?.Assignment?.Description ?? ""); - } - } - private string HelpText() - { - var groupNames = string.Join("\n- " , planner.LocalCourse?.Settings.AssignmentGroups.Select(g => g.Name) ?? []); - return $@" -SubmissionTypes: -- {AssignmentSubmissionType.ONLINE_TEXT_ENTRY} -- {AssignmentSubmissionType.ONLINE_UPLOAD} -- {AssignmentSubmissionType.DISCUSSION_TOPIC} -AllowedFileUploadExtensions: -- pdf -- jpg -- jpeg -- png - -Assignment Group Names: -- {groupNames} -"; - } -} - -
- @if(ShowHelp) - { -
-
-        @HelpText()
-      
-
- } - - @if(assignmentContext.Assignment != null && planner.LocalCourse != null) - { -
-
- - -
-
- @if (error != null) - { -

Error: @error

- } - - -
Due At: @assignmentContext.Assignment.DueAt
-
Lock At: @assignmentContext.Assignment.LockAt
-
Assignment Group Name @assignmentContext.Assignment.LocalAssignmentGroupName
-
Submission Types
-
    - @foreach(var t in assignmentContext.Assignment.SubmissionTypes) - { -
  • @t
  • - } -
-
-
- @(preview) -
-
- -
-
- } -
diff --git a/Management.Web/Pages/AssignmentForm/RubricDisplay.razor b/Management.Web/Pages/AssignmentForm/RubricDisplay.razor deleted file mode 100644 index cd6274d..0000000 --- a/Management.Web/Pages/AssignmentForm/RubricDisplay.razor +++ /dev/null @@ -1,61 +0,0 @@ -@using Management.Web.Shared.Components - -@inject CoursePlanner planner -@inject AssignmentEditorContext assignmentContext - -@code -{ - private string? error { get; set; } = null; - - protected override void OnInitialized() - { - assignmentContext.StateHasChanged += reload; - reload(); - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - - public void Dispose() - { - assignmentContext.StateHasChanged -= reload; - } - - private double requiredPoints => assignmentContext?.Assignment?.Rubric.Where(r => !r.IsExtraCredit).Select(r => r.Points).Sum() ?? 0; - private double extraCreditPoints => assignmentContext?.Assignment?.Rubric.Where(r => r.IsExtraCredit).Select(r => r.Points).Sum() ?? 0; -} - -@if(assignmentContext != null) -{ -
-

Rubric

-
- - @if (error != null) - { -

Error: @error

- } - -
-
Label
-
Points
-
Extra Credit
-
- @foreach (var item in assignmentContext?.Assignment?.Rubric ?? []) - { -
-
@item.Label
-
@item.Points
-
@item.IsExtraCredit
-
- } -
-
- Required Points: @requiredPoints -
-
- Extra Credit Points @extraCreditPoints -
-
-} diff --git a/Management.Web/Pages/AssignmentForm/SubmissionTypeSelector.razor b/Management.Web/Pages/AssignmentForm/SubmissionTypeSelector.razor deleted file mode 100644 index cace1d1..0000000 --- a/Management.Web/Pages/AssignmentForm/SubmissionTypeSelector.razor +++ /dev/null @@ -1,85 +0,0 @@ -@using System.Reflection -@inject AssignmentEditorContext assignmentContext - -@code -{ - protected override void OnInitialized() - { - assignmentContext.StateHasChanged += reload; - reload(); - } - private void reload() - { - if (assignmentContext.Assignment != null) - { - types = assignmentContext.Assignment.SubmissionTypes; - - } - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - assignmentContext.StateHasChanged -= reload; - } - private IEnumerable types { get; set; } = Enumerable.Empty(); - - private string getLabel(string type) - { - return type.ToString().Replace("_", "") + "switch"; - } - - private bool discussionIsSelected - { - get => types.FirstOrDefault( - t => t == AssignmentSubmissionType.DISCUSSION_TOPIC - ) != null; - } - private void saveTypes(IEnumerable newTypes) - { - if(assignmentContext.Assignment != null) - { - types = newTypes; - assignmentContext.SaveAssignment(assignmentContext.Assignment with - { - SubmissionTypes = types - }); - } - } - -} - -
Submission Types
-
- - @foreach (var submissionType in AssignmentSubmissionType.AllTypes) - { - var isDiscussion = submissionType == AssignmentSubmissionType.DISCUSSION_TOPIC; - var allowedToBeChecked = !discussionIsSelected || isDiscussion; - -
-
- - -
-
- } -
\ No newline at end of file diff --git a/Management.Web/Pages/CanvasRequestsQueue.razor b/Management.Web/Pages/CanvasRequestsQueue.razor deleted file mode 100644 index de47751..0000000 --- a/Management.Web/Pages/CanvasRequestsQueue.razor +++ /dev/null @@ -1,11 +0,0 @@ -@page "/test" -@rendermode InteractiveServer - -@inject ICanvasService canvas -@inject CoursePlanner planner -@inject IFileStorageManager fileStorageManager -@inject NavigationManager Navigation - -@code { - -} diff --git a/Management.Web/Pages/Course/AssignmentGroups.razor b/Management.Web/Pages/Course/AssignmentGroups.razor deleted file mode 100644 index 06fb286..0000000 --- a/Management.Web/Pages/Course/AssignmentGroups.razor +++ /dev/null @@ -1,142 +0,0 @@ -@using Management.Web.Shared.Components -@inject ICanvasService canvas -@inject CoursePlanner planner - - -@code { - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } - - private bool syncingAssignmentGroups { get; set; } = false; - private void AddAssignmentGroup() - { - if(planner.LocalCourse != null) - { - var newGroup = new LocalAssignmentGroup - { - Name = "", - Weight = 0, - Id = Guid.NewGuid().ToString() - }; - - var updatedGroups = planner.LocalCourse.Settings.AssignmentGroups.Append(newGroup); - planner.LocalCourse = planner.LocalCourse with - { - Settings = planner.LocalCourse.Settings with - { - AssignmentGroups = updatedGroups - } - }; - } - } - - private Action saveGroupName(string groupId) - { - return (e) => - { - if(planner.LocalCourse != null) - { - var newName = e.Value?.ToString() ?? ""; - var newGroups = planner.LocalCourse.Settings.AssignmentGroups.Select( - g => g.Id == groupId - ? g with { Name = newName } - : g - ); - planner.LocalCourse = planner.LocalCourse with - { - Settings = planner.LocalCourse.Settings with - { - AssignmentGroups = newGroups - } - }; - } - }; - } - private Action saveGroupWeight(string groupId) - { - return (e) => - { - if(planner.LocalCourse != null) - { - var newWeight = double.Parse(e.Value?.ToString() ?? "0"); - var newGroups = planner.LocalCourse.Settings.AssignmentGroups.Select( - g => g.Id == groupId - ? g with { Weight = newWeight } - : g - ); - planner.LocalCourse = planner.LocalCourse with - { - Settings = planner.LocalCourse.Settings with - { - AssignmentGroups = newGroups - } - }; - } - }; - } - - private async Task SyncAssignmentGroupsWithCanvas() - { - syncingAssignmentGroups = true; - await planner.SyncAssignmentGroups(); - syncingAssignmentGroups = false; - } -} - -@if(planner.LocalCourse != null) -{ -

Assignment Groups

- @foreach (var group in planner.LocalCourse.Settings.AssignmentGroups) - { - var groupName = group.Name; - var nameInputCallback = saveGroupName(group.Id); - var weight = group.Weight; - var weightInputCallback = saveGroupWeight(group.Id); -
-
- - -
-
- - -
-
- } -
- -
- - - @if(syncingAssignmentGroups) - { - - } -} diff --git a/Management.Web/Pages/Course/Course.razor b/Management.Web/Pages/Course/Course.razor deleted file mode 100644 index 59c70e6..0000000 --- a/Management.Web/Pages/Course/Course.razor +++ /dev/null @@ -1,81 +0,0 @@ -@page "/course/{CourseName}" -@using CanvasModel.EnrollmentTerms -@using CanvasModel.Courses -@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@using LocalModels -@using Management.Web.Pages.Course.Module.ModuleItems -@using Management.Web.Shared.Components - - -@inject IFileStorageManager fileStorageManager -@inject ICanvasService canvas -@inject CoursePlanner planner -@inject NavigationManager navigtion -@inject IConfiguration config - - -@code { - [Parameter] - public string? CourseName { get; set; } - - private bool loading = true; - - protected override async Task OnInitializedAsync() - { - if (planner.LocalCourse == null) - { - System.Diagnostics.Activity.Current = null; - using var activity = DiagnosticsConfig.Source?.StartActivity("Loading Course"); - activity?.AddTag("CourseName", CourseName); - var courses = await fileStorageManager.LoadSavedCourses(); - planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName); - } - base.OnInitialized(); - loading = false; - } - - private void selectNewCourse() - { - planner.Clear(); - navigtion.NavigateTo("/"); - } - -} - -@CourseName - - -
- - - @if (loading) - { - - } - - @if (planner.LocalCourse != null) - { -
-
- - - - View In Canvas - -
- @planner.LocalCourse.Settings.Name -
-
- - @if (planner.LoadingCanvasData) - { - - } -
- - } - -
diff --git a/Management.Web/Pages/Course/CourseCalendar/Day/AssignmentInDay.razor b/Management.Web/Pages/Course/CourseCalendar/Day/AssignmentInDay.razor deleted file mode 100644 index c437145..0000000 --- a/Management.Web/Pages/Course/CourseCalendar/Day/AssignmentInDay.razor +++ /dev/null @@ -1,54 +0,0 @@ -@using Management.Web.Course.Module.ModuleItems - -@inject DragContainer dragContainer -@inject NavigationManager Navigation -@inject AssignmentEditorContext assignmentContext - -@inject MyLogger logger - -@inherits DroppableAssignment - -@code { - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } - - private void HandleDragStart() - { - dragContainer.DropCallback = DropCallback; - } - - private void HandleDragEnd() - { - dragContainer.DropCallback = null; - } - - private void OnClick() - { - if(planner.LocalCourse != null) - { - assignmentContext.Assignment = Assignment; - Navigation.NavigateTo("/course/" + planner.LocalCourse.Settings.Name + "/assignment/" + Assignment.Name); - logger.Log("navigating to assignment page"); - } - } -} - -
  • - @Assignment.Name -
  • diff --git a/Management.Web/Pages/Course/CourseCalendar/Day/Day.razor b/Management.Web/Pages/Course/CourseCalendar/Day/Day.razor deleted file mode 100644 index be2492c..0000000 --- a/Management.Web/Pages/Course/CourseCalendar/Day/Day.razor +++ /dev/null @@ -1,148 +0,0 @@ -@inject DragContainer dragContainer -@inject CoursePlanner configurationManagement - -@inject CoursePlanner planner - -@code -{ - [Parameter, EditorRequired] - public DateTime? date { get; set; } = - default!; - - private bool isWeekDay { - get => date?.DayOfWeek != null; - } - private bool dragging {get; set;} = false; - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } - - private IEnumerable TodaysAssignments - { - get - { - if(planner.LocalCourse == null || date == null) - return Enumerable.Empty(); - else - return planner.LocalCourse.Modules - .SelectMany(m => m.Assignments) - .Where(a => a.DueAt.Date == date?.Date); - } - } - - private IEnumerable todaysQuizzes - { - get - { - if(planner.LocalCourse == null || date == null) - return Enumerable.Empty(); - else - return planner.LocalCourse.Modules - .SelectMany(m => m.Quizzes) - .Where(q => q.DueAt.Date == date?.Date); - } - } - - private IEnumerable todaysPages - { - get - { - if(planner.LocalCourse == null || date == null) - return Enumerable.Empty(); - else - return planner.LocalCourse.Modules - .SelectMany(m => m.Pages) - .Where(q => q.DueAt.Date == date?.Date); - } - } - - private string calculatedClass - { - get - { - var baseClasses = "col border rounded rounded-3 p-2 pb-4 m-1 "; - if(dragging) - return baseClasses + " bg-secondary text-light "; - - if(date?.Date == DateTime.Today) - baseClasses += " border-1 border-primary-subtle "; - - if (isWeekDay) - { - DayOfWeek? weekDay = date?.DayOfWeek; - DayOfWeek notNullDay = weekDay ?? default; - - var isClassDay = planner.LocalCourse?.Settings.DaysOfWeek.Contains(notNullDay) ?? false; - var dayInSemester = - isClassDay - && date <= planner.LocalCourse?.Settings.EndDate - && date >= planner.LocalCourse?.Settings.StartDate; - - var totalClasses = dayInSemester - ? "bg-light-subtle text-light " + baseClasses - : " " + baseClasses; - - return totalClasses; - } - else - { - return baseClasses; - } - } - } - void OnDragEnter() { - dragging = true; - } - void OnDragLeave() { - dragging = false; - } - - void OnDrop() - { - dragging = false; - if(dragContainer.DropCallback == null){ - System.Console.WriteLine("no drop callback set"); - return; - } - if(date != null) - { - DateTime d = date ?? throw new Exception("should not get here, error converting date from nullable"); - dragContainer.DropCallback?.Invoke(d, null); - } - } -} - -
    - @(isWeekDay ? date?.Day : "") -
      - @foreach (var assignment in TodaysAssignments) - { - - } - - @foreach(var quiz in todaysQuizzes) - { - - } - - @foreach(var page in todaysPages) - { - - } -
    -
    diff --git a/Management.Web/Pages/Course/CourseCalendar/Day/PageInDay.razor b/Management.Web/Pages/Course/CourseCalendar/Day/PageInDay.razor deleted file mode 100644 index 035cbb8..0000000 --- a/Management.Web/Pages/Course/CourseCalendar/Day/PageInDay.razor +++ /dev/null @@ -1,55 +0,0 @@ -@using Management.Web.Course.Module.ModuleItems - -@inject DragContainer dragContainer -@inject NavigationManager Navigation -@inject PageEditorContext pageContext - -@inject MyLogger logger - -@inherits DroppablePage - -@code { - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } - - private void HandleDragStart() - { - dragContainer.DropCallback = dropCallback; - } - - private void HandleDragEnd() - { - dragContainer.DropCallback = null; - } - - private void OnClick() - { - if(planner.LocalCourse != null) - { - pageContext.Page = Page; - Navigation.NavigateTo("/course/" + planner.LocalCourse.Settings.Name + "/page/" + Page.Name); - logger.Log("navigating to coursePage page"); - } - } -} - - -
  • - @Page.Name -
  • diff --git a/Management.Web/Pages/Course/CourseCalendar/Day/QuizInDay.razor b/Management.Web/Pages/Course/CourseCalendar/Day/QuizInDay.razor deleted file mode 100644 index 7b53ef4..0000000 --- a/Management.Web/Pages/Course/CourseCalendar/Day/QuizInDay.razor +++ /dev/null @@ -1,34 +0,0 @@ -@using Management.Web.Shared.Components.Quiz -@inject DragContainer dragContainer -@inject QuizEditorContext quizContext -@inject NavigationManager Navigation - -@inherits DroppableQuiz - -@code { - - private void HandleDragStart() - { - dragContainer.DropCallback = dropCallback; - } - - private void HandleDragEnd() - { - dragContainer.DropCallback = null; - } - private void OnClick() - { - quizContext.Quiz = Quiz; - Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name + "/quiz/" + Quiz.Name); - } -} - -
  • - @Quiz.Name -
  • \ No newline at end of file diff --git a/Management.Web/Pages/Course/CourseCalendar/MonthDetail.razor b/Management.Web/Pages/Course/CourseCalendar/MonthDetail.razor deleted file mode 100644 index 4988afb..0000000 --- a/Management.Web/Pages/Course/CourseCalendar/MonthDetail.razor +++ /dev/null @@ -1,55 +0,0 @@ -@using System.Linq -@using Management.Web.Pages.Course.CourseCalendar.Day - -@inject CoursePlanner planner - -@code -{ - [Parameter, EditorRequired] - public CalendarMonth Month { get; set; } = default!; - - public DayOfWeek[] WeekDaysList { get => (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek)); } - - public string MonthName { get => Month?.DaysByWeek.First().FirstOrDefault(d => d != null)?.ToString("MMMM") ?? ""; } - private string htmlLabel => "collapse"+MonthName; - private bool isInPast => - new DateTime(Month.Year, Month.Month, 1) < new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); - private string collapseClass => " collapse " + (isInPast ? "hide" : "show"); -} - -

    - - @MonthName - -

    - -
    -
    - @foreach (DayOfWeek day in WeekDaysList) - { -
    - @day -
    - } -
    - - @foreach (var week in Month.DaysByWeek) - { -
    - @foreach (var day in week) - { - - } -
    - } -
    diff --git a/Management.Web/Pages/Course/CourseDetails.razor b/Management.Web/Pages/Course/CourseDetails.razor deleted file mode 100644 index e66a515..0000000 --- a/Management.Web/Pages/Course/CourseDetails.razor +++ /dev/null @@ -1,57 +0,0 @@ - -@using CanvasModel.EnrollmentTerms -@using Management.Web.Pages.Course.Module -@using Management.Web.Pages.Course.CourseCalendar - -@inject ICanvasService canvas -@inject CoursePlanner planner - -@code -{ - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if(firstRender) - { - if( - planner.CanvasData == null - && planner.LocalCourse != null - && planner.LocalCourse.Settings.CanvasId != null - ) - { - await planner.LoadCanvasData(); - } - - } - } - - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } -} -
    -
    - @if (planner.LocalCourse != null) - { -
    - - @foreach (var month in SemesterPlanner.GetMonthsBetweenDates(planner.LocalCourse.Settings.StartDate, planner.LocalCourse.Settings.EndDate)) - { - -
    - } -
    - } -
    -
    - -
    -
    diff --git a/Management.Web/Pages/Course/CourseSettings.razor b/Management.Web/Pages/Course/CourseSettings.razor deleted file mode 100644 index 23039b0..0000000 --- a/Management.Web/Pages/Course/CourseSettings.razor +++ /dev/null @@ -1,186 +0,0 @@ -@using CanvasModel.Enrollments -@using Management.Web.Shared.Components -@inject ICanvasService canvas - -@inject CoursePlanner planner - -@code -{ - private Modal modal { get; set; } = default!; - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } - private IEnumerable? terms { get; set; } = null; - private IEnumerable? studentEnrollments { get; set; } = null; - private ulong? _selectedTermId {get; set;} - private ulong? selectedTermId { - get => _selectedTermId; - set - { - _selectedTermId = value; - if(selectedTerm != null && planner.LocalCourse != null) - { - planner.LocalCourse = planner.LocalCourse with - { - Settings = planner.LocalCourse.Settings with - { - StartDate=selectedTerm.StartAt ?? new DateTime(), - EndDate=selectedTerm.EndAt ?? new DateTime(), - } - }; - } - } - } - private EnrollmentTermModel? selectedTerm - { - get => terms?.FirstOrDefault(t => t.Id == selectedTermId); - } - private bool loading = false; - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - if(planner.LocalCourse != null && planner.LocalCourse.Settings.CanvasId != null) - { - loading = true; - ulong id = planner.LocalCourse?.Settings.CanvasId ?? throw new Exception("wtf how did i get here"); - var enrollmentsTask = canvas.GetEnrolledStudents(id); - var canvasCourse = await canvas.GetCourse(id); - terms = await canvas.GetCurrentTermsFor(canvasCourse.StartAt); - studentEnrollments = await enrollmentsTask; - - loading = false; - } - - } - } - -} - - - - - - <h1>Course Settings</h1> - - - -
    Select Days Of Week
    -
    - @foreach (DayOfWeek day in (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek))) - { -
    - -
    - } -
    - - @if(loading) - { - - } - - @if (terms != null) - { -
    -
    -
    - - -
    -
    -
    - } - - - @if(planner.LocalCourse != null) - { -
    -
    -
    Default Assignment Due Time
    - -
    -
    - } - - - @if(studentEnrollments != null) - { -
    - Students to import to github classroom: - @foreach(var enrollment in studentEnrollments) - { -
    - @(enrollment.User.DisplayName ?? enrollment.User.ShortName) -
    - } -
    - } - -
    - -
    -
    diff --git a/Management.Web/Pages/Course/Module/ModuleDetail.razor b/Management.Web/Pages/Course/Module/ModuleDetail.razor deleted file mode 100644 index 81e5d77..0000000 --- a/Management.Web/Pages/Course/Module/ModuleDetail.razor +++ /dev/null @@ -1,185 +0,0 @@ -@using Management.Web.Shared.Components -@using Management.Web.Shared.Components.Quiz -@using Management.Web.Pages.Course.Module -@using Management.Web.Pages.Course.Module.ModuleItems -@using Management.Web.Pages.Course.Module.NewItemsButtons -@using LocalModels -@using BlazorMonaco -@using BlazorMonaco.Editor - -@inject CoursePlanner configurationManagement -@inject CoursePlanner planner -@inject DragContainer dragContainer - -@code { - [Parameter, EditorRequired] - public LocalModule Module { get; set; } = default!; - private bool dragging { get; set; } = false; - private bool publishing = false; - private string _notes { get; set; } = ""; - private string notes - { - get => _notes; - set - { - if (value != _notes) - { - _notes = value; - if (planner.LocalCourse != null) - { - var newModule = Module with { Notes = _notes }; - var newModules = planner.LocalCourse.Modules.Select( - m => m.Name == newModule.Name - ? newModule - : m - ); - planner.LocalCourse = planner.LocalCourse with - { - Modules = newModules - }; - } - } - } - } - - - protected override void OnInitialized() - { - if (_notes == string.Empty) - { - _notes = Module.Notes; - } - planner.StateHasChanged += reload; - } - private void reload() - { - - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } - - private string accordionId - { - get => Module.Name.Replace(" ", "").Replace("#", "") + "-AccordionItem"; - } - - void OnDragEnter() - { - dragging = true; - } - void OnDragLeave() - { - dragging = false; - } - - void OnDrop() - { - dragging = false; - if (dragContainer.DropCallback == null) - { - System.Console.WriteLine("no drop callback set"); - return; - } - dragContainer.DropCallback?.Invoke(null, Module); - } - - private bool isSyncedWithCanvas => planner - .CanvasData? - .Modules - .FirstOrDefault( - cm => cm.Name == Module.Name - ) != null; - private async Task Publish() - { - publishing = true; - await planner.CreateModule(Module); - publishing = false; - } -} - -
    -

    - - -

    -
    -
    -
    -
    - -
    -
    - @if(publishing) - { - - } - else - { - if(!isSyncedWithCanvas) - { - - } - } -
    -
    - - - - -
    -
    -
    Assignments
    - -
    - @* @foreach(var p in Module.Pages) - { - - } - @foreach (var a in Module.Assignments) - { - - } -
    - @foreach (var quiz in Module.Quizzes) - { - - } *@ - @foreach(var item in Module.GetSortedModuleItems()) - { - @(item switch - { - LocalAssignment assignment => (@), - LocalQuiz quiz => (@), - LocalCoursePage page => (@), - _ => (@
    ) - }) - } -
    -
    -
    -
    diff --git a/Management.Web/Pages/Course/Module/ModuleItems/AssignmentListItem.razor b/Management.Web/Pages/Course/Module/ModuleItems/AssignmentListItem.razor deleted file mode 100644 index e39ff95..0000000 --- a/Management.Web/Pages/Course/Module/ModuleItems/AssignmentListItem.razor +++ /dev/null @@ -1,137 +0,0 @@ -@using Management.Web.Shared.Components -@using Management.Web.Course.Module.ModuleItems -@using CanvasModel.Assignments - -@inject DragContainer dragContainer -@inject NavigationManager Navigation -@inject AssignmentEditorContext assignmentContext - -@inherits DroppableAssignment - -@code { - [Parameter] - [EditorRequired] - public LocalModule Module { get; set; } = new(); - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } - private bool showAll { get; set; } = false; - - - private void HandleDragStart() - { - dragContainer.DropCallback = DropCallback; - } - - private void HandleDragEnd() - { - dragContainer.DropCallback = null; - } - - private CanvasAssignment? assignmentInCanvas => planner - .CanvasData? - .Assignments - .FirstOrDefault( - a => a.Name == Assignment.Name - ); - - private bool existsInCanvas => - assignmentInCanvas != null; - private void OnClick() - { - assignmentContext.Assignment = Assignment; - Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name + "/assignment/" + Assignment.Name); - } - - private bool NeedsToBeUpdatedInCanvas => planner.LocalCourse != null - && planner.LocalCourse.Settings.CanvasId != null - && planner.CanvasData != null - && assignmentInCanvas != null - && Assignment.NeedsUpdates( - (CanvasAssignment)assignmentInCanvas, - Assignment.GetCanvasAssignmentGroupId(planner.LocalCourse.Settings.AssignmentGroups) - ); -} - -
    - - @if( - planner.LocalCourse != null - && existsInCanvas - && NeedsToBeUpdatedInCanvas - && assignmentInCanvas != null - ) - { -
    - @Assignment.GetUpdateReason( - (CanvasAssignment)assignmentInCanvas, - Assignment.GetCanvasAssignmentGroupId(planner.LocalCourse.Settings.AssignmentGroups)) -
    - } - @if(!existsInCanvas) - { -
    - no assignment with same name in canvas -
    - } - - @if(!showAll) - { -
    -
    Points: @Assignment.PointsPossible
    -
    Due At: @Assignment.DueAt
    -
    - } - else - { -
    -
    - - @((MarkupString) @Assignment.GetDescriptionHtml()) -
    - -
    -
    Points: @Assignment.PointsPossible
    -
    Due At: @Assignment.DueAt
    -
    Lock At: @Assignment.LockAt
    -
    Submission Types:
    -
      - @foreach(var type in Assignment.SubmissionTypes) - { -
    • - @type -
    • - } -
    -
    -
    - } - - -
    - -
    - -
    -
    diff --git a/Management.Web/Pages/Course/Module/ModuleItems/DroppableAssignment.razor.cs b/Management.Web/Pages/Course/Module/ModuleItems/DroppableAssignment.razor.cs deleted file mode 100644 index 83f6e20..0000000 --- a/Management.Web/Pages/Course/Module/ModuleItems/DroppableAssignment.razor.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Microsoft.AspNetCore.Components; - -namespace Management.Web.Course.Module.ModuleItems; -public class DroppableAssignment : ComponentBase -{ - [Inject] - protected CoursePlanner planner { get; set; } = default!; - - [Parameter, EditorRequired] - public LocalAssignment Assignment { get; set; } = default!; - private void dropOnDate(DateTime dropDate) - { - if (planner.LocalCourse == null) return; - var currentModule = planner - .LocalCourse - .Modules - .First(m => - m.Assignments.Contains(Assignment) - ) ?? throw new Exception("in day callback, could not find module"); - - - var defaultDueTimeDate = new DateTime( - year: dropDate.Year, - month: dropDate.Month, - day: dropDate.Day, - hour: planner.LocalCourse.Settings.DefaultDueTime.Hour, - minute: planner.LocalCourse.Settings.DefaultDueTime.Minute, - second: 0 - ); - - var moduleWithUpdatedAssignment = currentModule with - { - Assignments = currentModule.Assignments.Select(a => - a.Name != Assignment.Name // we are only changing the due date, so the name should be the same - ? a - : a with - { - DueAt = defaultDueTimeDate, - LockAt = a.LockAt > defaultDueTimeDate ? a.LockAt : defaultDueTimeDate - } - ) - }; - var updatedModules = planner.LocalCourse.Modules - .Select(m => - m.Name == moduleWithUpdatedAssignment.Name - ? moduleWithUpdatedAssignment - : m - ); - var newCourse = planner.LocalCourse with - { - Modules = updatedModules - }; - planner.LocalCourse = newCourse; - } - private void dropOnModule(LocalModule module) - { - if (planner.LocalCourse == null) return; - var newModules = planner.LocalCourse.Modules.Select(m => - m.Name != module.Name - ? m with - { - Assignments = m.Assignments.Where(a => a.Name != Assignment.Name).DistinctBy(a => a.Name) - } - : m with - { - Assignments = m.Assignments.Append(Assignment).DistinctBy(a => a.Name) - } - ); - - var newCourse = planner.LocalCourse with - { - Modules = newModules - }; - planner.LocalCourse = newCourse; - } - - protected void DropCallback(DateTime? dropDate, LocalModule? module) - { - if (module == null) - { - dropOnDate(dropDate ?? Assignment.DueAt); - } - else - { - dropOnModule(module); - } - } - -} diff --git a/Management.Web/Pages/Course/Module/ModuleItems/DroppablePage.razor.cs b/Management.Web/Pages/Course/Module/ModuleItems/DroppablePage.razor.cs deleted file mode 100644 index 38a9b23..0000000 --- a/Management.Web/Pages/Course/Module/ModuleItems/DroppablePage.razor.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Microsoft.AspNetCore.Components; - -namespace Management.Web.Course.Module.ModuleItems; -public class DroppablePage : ComponentBase -{ - [Inject] - protected CoursePlanner planner { get; set; } = default!; - - [Parameter, EditorRequired] - public LocalCoursePage Page { get; set; } = default!; - private void dropOnDate(DateTime dropDate) - { - if (planner.LocalCourse == null) return; - var currentModule = planner - .LocalCourse - .Modules - .First(m => - m.Pages.Contains(Page) - ) ?? throw new Exception("in drop page callback, could not find module"); - - - var defaultDueTimeDate = new DateTime( - year: dropDate.Year, - month: dropDate.Month, - day: dropDate.Day, - hour: planner.LocalCourse.Settings.DefaultDueTime.Hour, - minute: planner.LocalCourse.Settings.DefaultDueTime.Minute, - second: 0 - ); - - var moduleWithUpdatedPage = currentModule with - { - Pages = currentModule.Pages.Select(p => - p.Name != Page.Name // we are only changing the due date, so the name should be the same - ? p - : p with { DueAt = defaultDueTimeDate } - ) - }; - var updatedModules = planner.LocalCourse.Modules - .Select(m => - m.Name == moduleWithUpdatedPage.Name - ? moduleWithUpdatedPage - : m - ); - var newCourse = planner.LocalCourse with - { - Modules = updatedModules - }; - planner.LocalCourse = newCourse; - } - private void dropOnModule(LocalModule module) - { - if (planner.LocalCourse == null) return; - var newModules = planner.LocalCourse.Modules.Select(m => - m.Name != module.Name - ? m with - { - Pages = m.Pages.Where(p => p.Name != Page.Name).DistinctBy(p => p.Name) - } - : m with - { - Pages = m.Pages.Append(Page).DistinctBy(a => a.Name) - } - ); - - var newCourse = planner.LocalCourse with - { - Modules = newModules - }; - planner.LocalCourse = newCourse; - } - - protected void dropCallback(DateTime? dropDate, LocalModule? module) - { - if (module == null) - { - dropOnDate(dropDate ?? Page.DueAt); - } - else - { - dropOnModule(module); - } - } - -} diff --git a/Management.Web/Pages/Course/Module/ModuleItems/DroppableQuiz.razor.cs b/Management.Web/Pages/Course/Module/ModuleItems/DroppableQuiz.razor.cs deleted file mode 100644 index ceb55fd..0000000 --- a/Management.Web/Pages/Course/Module/ModuleItems/DroppableQuiz.razor.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Runtime.CompilerServices; -using Microsoft.AspNetCore.Components; - -namespace Management.Web.Shared.Components.Quiz; - -public class DroppableQuiz : ComponentBase -{ - [Inject] - protected CoursePlanner planner { get; set; } = default!; - - [Parameter, EditorRequired] - public LocalQuiz Quiz { get; set; } = default!; - - - internal void dropCallback(DateTime? dropDate, LocalModule? dropModule) - { - if (dropDate != null) - { - dropOnDate(dropDate ?? throw new Exception("drop date for quiz is null")); - } - else if (dropModule != null) - { - dropOnModule(dropModule); - } - } - - private void dropOnDate(DateTime dropDate) - { - if (planner.LocalCourse == null) - return; - var currentModule = - planner.LocalCourse.Modules.First(m => m.Quizzes.Select(q => q.Name + q.Description).Contains(Quiz.Name + Quiz.Description)) - ?? throw new Exception("in quiz callback, could not find module"); - - var defaultDueTimeDate = new DateTime( - year: dropDate.Year, - month: dropDate.Month, - day: dropDate.Day, - hour: planner.LocalCourse.Settings.DefaultDueTime.Hour, - minute: planner.LocalCourse.Settings.DefaultDueTime.Minute, - second: 0 - ); - - var NewQuizList = currentModule.Quizzes - .Select(q => - q.Name + q.Description != Quiz.Name + Quiz.Description - ? q : - q with - { - DueAt = defaultDueTimeDate, - LockAt = q.LockAt > defaultDueTimeDate ? q.LockAt : defaultDueTimeDate - } - ) - .ToArray(); - - var updatedModule = currentModule with { Quizzes = NewQuizList }; - var updatedModules = planner.LocalCourse.Modules - .Select(m => m.Name == updatedModule.Name ? updatedModule : m) - .ToArray(); - - planner.LocalCourse = planner.LocalCourse with { Modules = updatedModules }; - } - - private void dropOnModule(LocalModule dropModule) - { - if (planner.LocalCourse == null) - return; - var newModules = planner.LocalCourse.Modules - .Select( - m => - m.Name != dropModule.Name - ? m with - { - Quizzes = m.Quizzes.Where(q => q.Name + q.Description != Quiz.Name + Quiz.Description).DistinctBy(q => q.Name + q.Description) - } - : m with - { - Quizzes = m.Quizzes.Append(Quiz).DistinctBy(q => q.Name + q.Description) - } - ) - .ToArray(); - - var newCourse = planner.LocalCourse with { Modules = newModules }; - planner.LocalCourse = newCourse; - } -} diff --git a/Management.Web/Pages/Course/Module/ModuleItems/ModuleItemLayout.razor b/Management.Web/Pages/Course/Module/ModuleItems/ModuleItemLayout.razor deleted file mode 100644 index 810a966..0000000 --- a/Management.Web/Pages/Course/Module/ModuleItems/ModuleItemLayout.razor +++ /dev/null @@ -1,32 +0,0 @@ -@using Management.Web.Shared.Components - - -@code { - [Parameter] - public RenderFragment ChildContent { get; set; } = default!; - - [Parameter, EditorRequired] - public string Name { get; set; } = default!; - - [Parameter, EditorRequired] - public bool IsSyncedWithCanvas { get; set; } = default!; -} - - -
    -
    -
    -

    @Name

    - @if(IsSyncedWithCanvas) - { - - } - else - { - - } -
    - @ChildContent -
    - -
    diff --git a/Management.Web/Pages/Course/Module/ModuleItems/PageListItem.razor b/Management.Web/Pages/Course/Module/ModuleItems/PageListItem.razor deleted file mode 100644 index c6d87b7..0000000 --- a/Management.Web/Pages/Course/Module/ModuleItems/PageListItem.razor +++ /dev/null @@ -1,48 +0,0 @@ -@using Management.Web.Shared.Components -@using Management.Web.Course.Module.ModuleItems - -@inject DragContainer dragContainer -@inject NavigationManager Navigation -@inject PageEditorContext pageContext - -@inherits DroppablePage - -@code { - private void HandleDragStart() - { - dragContainer.DropCallback = dropCallback; - } - - private void HandleDragEnd() - { - dragContainer.DropCallback = null; - } - private bool existsInCanvas => false; - - - private void OnClick() - { - pageContext.Page = Page; - Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name + "/page/" + Page.Name); - } -} - -
    - - @if(!existsInCanvas) - { -
    - no page with same name in canvas -
    - } -
    -
    Due At: @Page.DueAt
    -
    -
    -
    diff --git a/Management.Web/Pages/Course/Module/ModuleItems/QuizListItem.razor b/Management.Web/Pages/Course/Module/ModuleItems/QuizListItem.razor deleted file mode 100644 index 2db4ab9..0000000 --- a/Management.Web/Pages/Course/Module/ModuleItems/QuizListItem.razor +++ /dev/null @@ -1,54 +0,0 @@ -@using Management.Web.Shared.Components -@using Management.Web.Shared.Components.Quiz - -@inject DragContainer dragContainer -@inject QuizEditorContext quizContext -@inject NavigationManager Navigation - -@inherits DroppableQuiz - -@code { - private void HandleDragStart() - { - dragContainer.DropCallback = dropCallback; - } - - private void HandleDragEnd() - { - dragContainer.DropCallback = null; - } - private bool existsInCanvas => - planner.CanvasData != null - ? Quiz.QuizIsCreated(planner.CanvasData.Quizzes) - : false; - - - private void OnClick() - { - quizContext.Quiz = Quiz; - Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name + "/quiz/" + Quiz.Name); - } -} - - - -
    - - @if(!existsInCanvas) - { -
    - no quiz with same name in canvas -
    - } - -
    -
    Due At: @Quiz.DueAt
    -
    -
    -
    diff --git a/Management.Web/Pages/Course/Module/Modules.razor b/Management.Web/Pages/Course/Module/Modules.razor deleted file mode 100644 index 898b3e1..0000000 --- a/Management.Web/Pages/Course/Module/Modules.razor +++ /dev/null @@ -1,51 +0,0 @@ -@using Management.Web.Pages.Course.Module -@using System.Linq -@using Management.Web.Pages.Course.Module.NewItemsButtons -@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage - -@inject CoursePlanner planner - -@code { - private bool showNewModule { get; set; } = false; - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } -} - -
    -
    - @if (!showNewModule) - { - - } - else - { - - } -
    -
    - -@if (showNewModule) -{ - -} - -@if (planner.LocalCourse != null) -{ -
    - - @foreach (var module in planner.LocalCourse.Modules) - { - - } -
    -} diff --git a/Management.Web/Pages/Course/Module/NewItemsButtons/NewAssignment.razor b/Management.Web/Pages/Course/Module/NewItemsButtons/NewAssignment.razor deleted file mode 100644 index 81da632..0000000 --- a/Management.Web/Pages/Course/Module/NewItemsButtons/NewAssignment.razor +++ /dev/null @@ -1,95 +0,0 @@ -@using Management.Web.Shared.Components -@using Management.Web.Shared.Components.Forms - -@inject CoursePlanner planner - - -@code { - - [Parameter] - [EditorRequired] - public LocalModule Module { get; set; } = default!; - - [Required] - [StringLength(50, ErrorMessage = "Name too long (50 character limit).")] - private string Name { get; set; } = ""; - - private Modal? modal { get; set; } = null; - - private void submitHandler() - { - System.Console.WriteLine("new assignment"); - var newAssignment = new LocalAssignment - { - Name = Name, - Description = "", - @* LockAtDueDate = true, *@ - Rubric = new RubricItem[] { }, - LockAt = null, - DueAt = DateTime.Now, - SubmissionTypes = new string[] { AssignmentSubmissionType.ONLINE_TEXT_ENTRY }, - LocalAssignmentGroupName = selectedAssignmentGroup?.Name, - }; - - if(planner.LocalCourse != null) - { - var newModules =planner.LocalCourse.Modules.Select(m => - m.Name != Module.Name - ? m - : Module with - { - Assignments=Module.Assignments.Append(newAssignment) - } - ); - planner.LocalCourse = planner.LocalCourse with - { - Modules=newModules - }; - } - Name = ""; - modal?.Hide(); - } - - private void setAssignmentGroup(LocalAssignmentGroup? group) - { - selectedAssignmentGroup = group; - } - private LocalAssignmentGroup? selectedAssignmentGroup { get; set; } -} - - - - - New Assignment - -
    - - -
    -
    - - @if(planner != null) - { - - } - -
    - -
    -
    \ No newline at end of file diff --git a/Management.Web/Pages/Course/Module/NewItemsButtons/NewModule.razor b/Management.Web/Pages/Course/Module/NewItemsButtons/NewModule.razor deleted file mode 100644 index a34a00b..0000000 --- a/Management.Web/Pages/Course/Module/NewItemsButtons/NewModule.razor +++ /dev/null @@ -1,38 +0,0 @@ -@inject CoursePlanner planner -@inject ICanvasService canvas - -@code { - - [Required] - [StringLength(50, ErrorMessage = "Name too long (50 character limit).")] - private string Name { get; set; } = ""; - - [Parameter] - public EventCallback OnSubmit { get; set; } - - private async Task submitHandler() - { - if(planner.LocalCourse != null && Name != "") - { - var newModule = new LocalModule - { - Name=Name - }; - - planner.LocalCourse = planner.LocalCourse with - { - Modules = planner.LocalCourse.Modules.Append(newModule) - }; - } - Name = ""; - await OnSubmit.InvokeAsync(); - } -} - -

    New Module

    - -
    - - - -
    diff --git a/Management.Web/Pages/Course/Module/NewItemsButtons/NewPage.razor b/Management.Web/Pages/Course/Module/NewItemsButtons/NewPage.razor deleted file mode 100644 index 580e44b..0000000 --- a/Management.Web/Pages/Course/Module/NewItemsButtons/NewPage.razor +++ /dev/null @@ -1,76 +0,0 @@ -@using Management.Web.Shared.Components -@using Management.Web.Shared.Components.Forms - -@inject CoursePlanner planner - -@code { - - [Parameter] - [EditorRequired] - public LocalModule Module { get; set; } = default!; - - [Required] - [StringLength(50, ErrorMessage = "Name too long (50 character limit).")] - private string Name { get; set; } = ""; - - private Modal? modal { get; set; } = null; - - private void submitHandler() - { - DiagnosticsConfig.Source?.StartActivity("Creating Page"); - - - if(planner.LocalCourse != null) - { - var newPage = new LocalCoursePage - { - Name = Name, - Text = "", - DueAt = DateTime.Now - }; - - var newModules =planner.LocalCourse.Modules.Select(m => - m.Name != Module.Name - ? m - : Module with - { - Pages=Module.Pages.Append(newPage) - } - ); - planner.LocalCourse = planner.LocalCourse with - { - Modules=newModules - }; - } - Name = ""; - modal?.Hide(); - } -} - - - - - New Page - -
    - - -
    -
    - - -
    - -
    -
    diff --git a/Management.Web/Pages/Course/Module/NewItemsButtons/NewQuiz.razor b/Management.Web/Pages/Course/Module/NewItemsButtons/NewQuiz.razor deleted file mode 100644 index 10094e4..0000000 --- a/Management.Web/Pages/Course/Module/NewItemsButtons/NewQuiz.razor +++ /dev/null @@ -1,92 +0,0 @@ -@using Management.Web.Shared.Components -@using Management.Web.Shared.Components.Forms - -@inject CoursePlanner planner -@code { - - [Parameter] - [EditorRequired] - public LocalModule Module { get; set; } = default!; - - [Required] - [StringLength(50, ErrorMessage = "Name too long (50 character limit).")] - private string Name { get; set; } = ""; - - private Modal? modal { get; set; } = null; - - private void submitHandler() - { - Console.WriteLine("new quiz"); - Console.WriteLine(selectedAssignmentGroup); - if(Name.Trim() == string.Empty) - { - return; - } - - - var newQuiz = new LocalQuiz - { - Name=Name, - Description = "", - LocalAssignmentGroupName = selectedAssignmentGroup?.Name, - }; - if(planner.LocalCourse != null) - { - var newModules = planner.LocalCourse.Modules.Select(m => - m.Name != Module.Name - ? m - : Module with - { - Quizzes=Module.Quizzes.Append(newQuiz) - } - ); - planner.LocalCourse = planner.LocalCourse with - { - Modules=newModules - }; - } - modal?.Hide(); - } - - private void setAssignmentGroup(LocalAssignmentGroup? group) - { - selectedAssignmentGroup = group; - } - private LocalAssignmentGroup? selectedAssignmentGroup { get; set; } -} - - - - - New Quiz - -
    - - -
    -
    - - @if(planner != null && planner.LocalCourse != null) - { - - } - -
    - -
    -
    \ No newline at end of file diff --git a/Management.Web/Pages/Course/Module/RenameModule.razor b/Management.Web/Pages/Course/Module/RenameModule.razor deleted file mode 100644 index b0fc7e5..0000000 --- a/Management.Web/Pages/Course/Module/RenameModule.razor +++ /dev/null @@ -1,66 +0,0 @@ -@using Management.Web.Shared.Components - -@inject CoursePlanner planner - -@code { - - [Parameter] - [EditorRequired] - public LocalModule Module { get; set; } = default!; - private Modal? modal { get; set; } = null; - private string Name { get; set; } = string.Empty; - - protected override void OnParametersSet() - { - if (Name == string.Empty) - Name = Module.Name; - } - - private void submitHandler() - { - if (planner.LocalCourse == null) - return; - - var newModule = Module with - { - Name = Name - }; - - // Module is the not renamed version - var newModules = planner.LocalCourse.Modules.Select( - m => m.Name == Module.Name - ? newModule - : m - ).ToArray(); - - planner.LocalCourse = planner.LocalCourse with - { - Modules = newModules - }; - Name = ""; - modal?.Hide(); - } -} - - - - - Rename Module - - -
    - - -
    - -
    - -
    -
    \ No newline at end of file diff --git a/Management.Web/Pages/CoursePageForm/CoursePageForm.razor b/Management.Web/Pages/CoursePageForm/CoursePageForm.razor deleted file mode 100644 index ced9760..0000000 --- a/Management.Web/Pages/CoursePageForm/CoursePageForm.razor +++ /dev/null @@ -1,201 +0,0 @@ -@using Management.Web.Shared.Components -@using CanvasModel.Pages - -@inject CoursePlanner planner -@inject ICanvasService canvas -@inject NavigationManager Navigation -@inject PageEditorContext pageContext - - -@code { - protected override void OnInitialized() - { - pageContext.StateHasChanged += reload; - reload(); - } - private void reload() - { - if (pageContext.Page != null) - { - name = pageContext.Page.Name; - } - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - pageContext.StateHasChanged -= reload; - } - - private string name { get; set; } = String.Empty; - private bool addingPageToCanvas = false; - private bool deletingPageFromCanvas = false; - - - private CanvasPage? pageInCanvas => - planner.CanvasData?.Pages.FirstOrDefault(a => a.Title == pageContext.Page?.Name); - - - private string canvasPageUrl => - $"https://snow.instructure.com/courses/{planner.LocalCourse?.Settings.CanvasId}/assignments/{pageInCanvas?.PageId}"; - - - private void submitHandler() - { - if (pageContext.Page != null) - { - var newPage = pageContext.Page with - { - Name = name, - }; - - pageContext.SavePage(newPage); - } - pageContext.Page = null; - } - private async Task HandleDelete() - { - - if (planner.LocalCourse != null && pageContext.Page != null) - { - var page = pageContext.Page; - - var currentModule = planner - .LocalCourse - .Modules - .First(m => - m.Pages.Contains(page) - ) ?? throw new Exception("handling page delete, could not find module"); - - var newModules = planner.LocalCourse.Modules.Select(m => - m.Name == currentModule.Name - ? m with - { - Pages = m.Pages.Where(p => p != page).ToArray() - } - : m - ) - .ToArray(); - - planner.LocalCourse = planner.LocalCourse with - { - Modules = newModules - }; - - if (pageInCanvas != null && planner.LocalCourse.Settings.CanvasId != null) - { - ulong courseId = planner.LocalCourse.Settings.CanvasId ?? throw new Exception("cannot delete if no course id"); - await canvas.Pages.Delete(courseId, pageInCanvas.PageId); - } - Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name); - } - } - - private void handleNameChange(ChangeEventArgs e) - { - if (pageContext.Page != null) - { - var newPage = pageContext.Page with { Name = e.Value?.ToString() ?? "" }; - pageContext.SavePage(newPage); - } - } - - private async Task addToCanvas() - { - addingPageToCanvas = true; - await pageContext.AddPageToCanvas(); - await planner.LoadCanvasData(); - addingPageToCanvas = false; - } - private async Task updateInCanvas() - { - if(pageInCanvas != null) - { - addingPageToCanvas = true; - await pageContext.UpdateInCanvas(pageInCanvas.PageId); - await planner.LoadCanvasData(); - addingPageToCanvas = false; - } - } - - - private async Task deleteFromCanvas() - { - if (pageInCanvas == null - || planner?.LocalCourse?.Settings.CanvasId == null - || pageContext.Page == null - ) - return; - - deletingPageFromCanvas = true; - await canvas.Pages.Delete( - (ulong)planner.LocalCourse.Settings.CanvasId, - pageInCanvas.PageId - ); - await planner.LoadCanvasData(); - deletingPageFromCanvas = false; - StateHasChanged(); - } -} - -
    -
    - @pageContext.Page?.Name -
    - -
    - @if (pageContext.Page != null) - { - - } -
    - -
    - @if (addingPageToCanvas || deletingPageFromCanvas) - { -
    - -
    - } - - - - @if (pageInCanvas != null) - { - - View in Canvas - - - - } - -
    - - -
    diff --git a/Management.Web/Pages/CoursePageForm/CoursePageFormPage.razor b/Management.Web/Pages/CoursePageForm/CoursePageFormPage.razor deleted file mode 100644 index 069d222..0000000 --- a/Management.Web/Pages/CoursePageForm/CoursePageFormPage.razor +++ /dev/null @@ -1,71 +0,0 @@ -@page "/course/{CourseName}/page/{PageName}" - -@using CanvasModel.EnrollmentTerms -@using CanvasModel.Courses -@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@using LocalModels -@using Management.Web.Pages.Course.Module.ModuleItems -@using Management.Web.Shared.Components - -@inject IFileStorageManager fileStorageManager -@inject ICanvasService canvas -@inject CoursePlanner planner -@inject PageEditorContext pageContext -@inject ILogger logger - - - -@code { - [Parameter] - public string? CourseName { get; set; } = default!; - [Parameter] - public string? PageName { get; set; } = default!; - - - private bool loading { get; set; } = true; - - protected override async Task OnInitializedAsync() - { - if (loading) - { - loading = false; - logger.LogInformation($"loading page {CourseName} {PageName}"); - if (planner.LocalCourse == null) - { - var courses = await fileStorageManager.LoadSavedCourses(); - planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName); - logger.LogInformation($"set course to '{planner.LocalCourse?.Settings.Name}'"); - } - - if (pageContext.Page == null) - { - var page = planner - .LocalCourse? - .Modules - .SelectMany(m => m.Pages) - .FirstOrDefault(a => a.Name == PageName); - - pageContext.Page = page; - logger.LogInformation($"set page to '{pageContext.Page?.Name}'"); - } - await planner.LoadCanvasData(); - base.OnInitialized(); - StateHasChanged(); - } - } -} - - -@CourseName - @PageName - -
    - @if (loading) - { - - } - - @if (planner.LocalCourse != null && pageContext.Page != null) - { - - } -
    diff --git a/Management.Web/Pages/CoursePageForm/CoursePageMarkdownEditor.razor b/Management.Web/Pages/CoursePageForm/CoursePageMarkdownEditor.razor deleted file mode 100644 index 87927a5..0000000 --- a/Management.Web/Pages/CoursePageForm/CoursePageMarkdownEditor.razor +++ /dev/null @@ -1,78 +0,0 @@ -@using Management.Web.Shared.Components - -@inject CoursePlanner planner -@inject PageEditorContext pageContext - -@code { - protected override void OnInitialized() - { - pageContext.StateHasChanged += reload; - reload(); - } - private void reload() - { - if (pageContext.Page != null) - { - if(rawText == string.Empty) - { - rawText = pageContext.Page.ToMarkdown(); - this.InvokeAsync(this.StateHasChanged); - } - } - } - public void Dispose() - { - pageContext.StateHasChanged -= reload; - } - - private string rawText { get; set; } = string.Empty; - private string? error = null; - - private MarkupString preview { get => (MarkupString)MarkdownService.Render(pageContext?.Page?.Text ?? ""); } - - private void handleChange(string newRawPage) - { - rawText = newRawPage; - if (newRawPage != string.Empty) - { - try - { - var parsed = LocalCoursePage.ParseMarkdown(newRawPage); - error = null; - pageContext.SavePage(parsed); - } - catch(LocalPageMarkdownParseException e) - { - error = e.Message; - } - finally - { - StateHasChanged(); - } - } - StateHasChanged(); - } -} - -
    - @if(pageContext.Page != null && planner.LocalCourse != null) - { -
    -
    - - -
    -
    - @if (error != null) - { -

    Error: @error

    - } -
    Due At: @pageContext.Page.DueAt
    -
    -
    - @(preview) -
    -
    -
    - } -
    diff --git a/Management.Web/Pages/Error.cshtml b/Management.Web/Pages/Error.cshtml deleted file mode 100644 index d50318d..0000000 --- a/Management.Web/Pages/Error.cshtml +++ /dev/null @@ -1,42 +0,0 @@ -@page -@model Management.Web.Pages.ErrorModel - - - - - - - - Error - - - - - -
    -
    -

    Error.

    -

    An error occurred while processing your request.

    - - @if (Model.ShowRequestId) - { -

    - Request ID: @Model.RequestId -

    - } - -

    Development Mode

    -

    - Swapping to the Development environment displays detailed information about the error that occurred. -

    -

    - The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

    -
    -
    - - - diff --git a/Management.Web/Pages/Error.cshtml.cs b/Management.Web/Pages/Error.cshtml.cs deleted file mode 100644 index 035afba..0000000 --- a/Management.Web/Pages/Error.cshtml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Diagnostics; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Management.Web.Pages; - -[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] -[IgnoreAntiforgeryToken] -public class ErrorModel : PageModel -{ - public string? RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) - { - _logger = logger; - } - - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } -} diff --git a/Management.Web/Pages/Index.razor b/Management.Web/Pages/Index.razor deleted file mode 100644 index 76aca8a..0000000 --- a/Management.Web/Pages/Index.razor +++ /dev/null @@ -1,81 +0,0 @@ -@page "/" -@using CanvasModel.EnrollmentTerms -@using CanvasModel.Courses -@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@using LocalModels -@using Management.Web.Pages.Course.Module.ModuleItems -@using Management.Web.Shared.Components - -@inject ICanvasService canvas -@inject CoursePlanner planner - - -@code { - private bool showNewFile { get; set; } = false; - protected override void OnInitialized() - { - planner.LocalCourse = null; - planner.StateHasChanged += reload; - } - - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - - public void Dispose() - { - planner.StateHasChanged -= reload; - } - private void NewFileCreated() - { - showNewFile = false; - refreshKey++; - StateHasChanged(); - } - private int refreshKey; - -} - -Index - -
    -@if(planner.LocalCourse == null) -{ -
    -
    - -
    -
    - - @if(!showNewFile) - { -
    - -
    - } - - @if(showNewFile) - { -
    - -
    - -
    - -
    - } -} -
    - - diff --git a/Management.Web/Pages/QuizForm/MarkdownQuestionPreview.razor b/Management.Web/Pages/QuizForm/MarkdownQuestionPreview.razor deleted file mode 100644 index 591f60c..0000000 --- a/Management.Web/Pages/QuizForm/MarkdownQuestionPreview.razor +++ /dev/null @@ -1,81 +0,0 @@ - -@code { - [Parameter, EditorRequired] - public LocalQuizQuestion Question { get; set; } = default!; - -} - -
    -
    - points: @Question.Points -
    -
    - @Question.QuestionType -
    -
    - - -@((MarkupString)Question.HtmlText) - -@if(Question.QuestionType == QuestionType.MATCHING) -{ - @foreach(var answer in Question.Answers) - { -
    -
    - @answer.Text -
    -
    - @answer.MatchedText -
    -
    - } -} -else -{ - @foreach(var answer in Question.Answers) - { - string answerPreview = answer.HtmlText.StartsWith("

    ") - ? answer.HtmlText.Replace("

    ", "

    ") - : answer.HtmlText; - -

    - @if(answer.Correct) - { - - - - } - else - { -
    - @if(Question.QuestionType == QuestionType.MULTIPLE_ANSWERS) - { - [ ] - } -
    - } -
    - @((MarkupString)answerPreview) -
    -
    - } -} diff --git a/Management.Web/Pages/QuizForm/MarkdownQuizForm.razor b/Management.Web/Pages/QuizForm/MarkdownQuizForm.razor deleted file mode 100644 index c2e7d47..0000000 --- a/Management.Web/Pages/QuizForm/MarkdownQuizForm.razor +++ /dev/null @@ -1,79 +0,0 @@ -@using Management.Web.Shared.Components - -@inject QuizEditorContext quizContext - -@code { - private Modal? modal { get; set; } - - private LocalQuiz? testQuiz; - - private string? error { get; set; } = null; - private string _quizMarkdownInput { get; set; } = ""; - private string quizMarkdownInput - { - get => _quizMarkdownInput; - set - { - _quizMarkdownInput = value; - - try - { - var newQuiz = LocalQuiz.ParseMarkdown(_quizMarkdownInput); - error = null; - testQuiz = newQuiz; - quizContext.SaveQuiz(newQuiz); - } - catch (QuizMarkdownParseException e) - { - error = e.Message; - StateHasChanged(); - } - } - } - - protected override void OnInitialized() - { - reload(); - quizContext.StateHasChanged += reload; - } - private void reload() - { - if (quizContext.Quiz != null) - { - if (quizMarkdownInput == "") - { - quizMarkdownInput = quizContext.Quiz.ToMarkdown(); - } - this.InvokeAsync(this.StateHasChanged); - } - } - public void Dispose() - { - quizContext.StateHasChanged -= reload; - } -} - -
    -
    -
    -
    - - -
    -
    - @if (error != null) - { -

    Error: @error

    - } - @if(testQuiz != null) - { - - } -
    -
    -
    - -
    \ No newline at end of file diff --git a/Management.Web/Pages/QuizForm/QuizFormPage.razor b/Management.Web/Pages/QuizForm/QuizFormPage.razor deleted file mode 100644 index a95bfd3..0000000 --- a/Management.Web/Pages/QuizForm/QuizFormPage.razor +++ /dev/null @@ -1,249 +0,0 @@ -@page "/course/{CourseName}/quiz/{QuizName}" - -@using CanvasModel.EnrollmentTerms -@using CanvasModel.Quizzes -@using Management.Web.Shared.Components -@using CanvasModel.Courses -@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@using LocalModels -@using Management.Web.Pages.Course.Module.ModuleItems - -@inject IFileStorageManager fileStorageManager -@inject ICanvasService canvas -@inject CoursePlanner planner -@inject QuizEditorContext quizContext -@inject MyLogger logger -@inject NavigationManager Navigation - -@code { - [Parameter] - public string? CourseName { get; set; } = default!; - [Parameter] - public string? QuizName { get; set; } = default!; - - private bool loading { get; set; } = true; - private bool addingQuizToCanvas = false; - - protected override void OnInitialized() - { - quizContext.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - quizContext.StateHasChanged -= reload; - } - protected override async Task OnInitializedAsync() - { - if (loading) - { - loading = false; - logger.Log($"loading quiz {CourseName} {QuizName}"); - if (planner.LocalCourse == null) - { - var courses = await fileStorageManager.LoadSavedCourses(); - planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName); - logger.Log($"set course to '{planner.LocalCourse?.Settings.Name}'"); - } - - if (quizContext.Quiz == null) - { - var quiz = planner - .LocalCourse? - .Modules - .SelectMany(m => m.Quizzes) - .FirstOrDefault(q => q.Name == QuizName); - - quizContext.Quiz = quiz; - logger.Log($"set quiz to '{quizContext.Quiz?.Name}'"); - } - StateHasChanged(); - - if (planner.CanvasData == null) - { - await planner.LoadCanvasData(); - } - - base.OnInitialized(); - StateHasChanged(); - } - } - - private void deleteQuiz() - { - quizContext.DeleteQuiz(); - Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name); - } - - private async Task addToCanvas() - { - addingQuizToCanvas = true; - await quizContext.AddQuizToCanvas(); - await planner.LoadCanvasData(); - addingQuizToCanvas = false; - } - private void done() - { - quizContext.Quiz = null; - Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name); - } - - private CanvasQuiz? quizInCanvas => planner.CanvasData?.Quizzes.FirstOrDefault(q => q.Title == quizContext.Quiz?.Name); - - private string canvasQuizUrl => - $"https://snow.instructure.com/courses/{planner.LocalCourse?.Settings.CanvasId}/quizzes/{quizInCanvas?.Id}"; - - private double? quizPoints => quizContext.Quiz?.Questions.Sum(q => q.Points); - private bool showHelp = false; - - private readonly static string exampleMarkdownQuestion = @"QUESTION REFERENCE ---- -Points: 2 -this is a question? -*a) correct -b) not correct ---- -points: 1 -question goes here -[*] correct -[ ] not correct -[] not correct ---- -the points default to 1? -*a) true -b) false ---- -Markdown is supported - -- like -- this -- list - -[*] true -[ ] false ---- -This is a one point essay question -essay ---- -points: 4 -this is a short answer question -short_answer ---- -points: 4 -the underscore is optional -short answer ---- -this is a matching question -^ left answer - right dropdown -^ other thing - another option -"; -} - -
    - -
    -
    -
    - -
    -
    -

    - @quizContext.Quiz?.Name -

    -
    - - @if (quizContext.Quiz == null) - { -
    - -
    - } -
    -

    - Questions: @quizContext.Quiz?.Questions.Count() - Points: @quizPoints -

    - @if (quizInCanvas != null) - { - @if (quizInCanvas?.Published == true) - { -
    - Published! -
    - } - else - { -
    - Not Published -
    - } - } -
    -
    -
    - -
    - - @if(showHelp) - { -
    -        @exampleMarkdownQuestion
    -      
    - } -
    - @if (quizContext.Quiz != null) - { - - } -
    -
    - -
    - -
    - -
    - @if (quizContext.Quiz != null) - { -
    -
    - - - @if (quizInCanvas != null) - { - - View in Canvas - - } - -
    -
    - } - - @if (addingQuizToCanvas) - { - - } -
    -
    diff --git a/Management.Web/Pages/QuizForm/QuizPreview.razor b/Management.Web/Pages/QuizForm/QuizPreview.razor deleted file mode 100644 index b85e8b2..0000000 --- a/Management.Web/Pages/QuizForm/QuizPreview.razor +++ /dev/null @@ -1,71 +0,0 @@ -@using Management.Web.Shared.Components - -@inject QuizEditorContext quizContext - - -@code { - - [Parameter, EditorRequired] - public LocalQuiz Quiz { get; set; } = default!; - protected override void OnInitialized() - { - quizContext.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - quizContext.StateHasChanged -= reload; - } -} - - -@if(Quiz != null) -{ -
    -
    -
    -
    Name:
    -
    @Quiz.Name
    -
    -
    -
    Due At:
    -
    @Quiz.DueAt
    -
    -
    -
    Lock At:
    -
    @Quiz.LockAt
    -
    -
    -
    Shuffle Answers:
    -
    @Quiz.ShuffleAnswers
    -
    -
    -
    Allowed Attempts:
    -
    @Quiz.AllowedAttempts
    -
    -
    -
    One question at a time:
    -
    @Quiz.OneQuestionAtATime
    -
    -
    -
    Assignment Group:
    -
    @Quiz.LocalAssignmentGroupName
    -
    -
    -
    - -
    @Quiz.Description
    - - @foreach(var question in Quiz.Questions) - { -
    - -
    - } -} \ No newline at end of file diff --git a/Management.Web/Pages/_Host.cshtml b/Management.Web/Pages/_Host.cshtml deleted file mode 100644 index 77bda7d..0000000 --- a/Management.Web/Pages/_Host.cshtml +++ /dev/null @@ -1,55 +0,0 @@ -@page "/" -@using Microsoft.AspNetCore.Components.Web -@namespace Management.Web.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - An error has occurred. This application may no longer respond until reloaded. - - - An unhandled exception has occurred. See browser dev tools for details. - - Reload - 🗙 -
    - - - - - - - - diff --git a/Management.Web/Program.cs b/Management.Web/Program.cs deleted file mode 100644 index 71b0aff..0000000 --- a/Management.Web/Program.cs +++ /dev/null @@ -1,164 +0,0 @@ -global using System.ComponentModel.DataAnnotations; -global using System.Text.Json; -global using System.Text.Json.Serialization; - -global using CanvasModel; -global using CanvasModel.Courses; -global using CanvasModel.EnrollmentTerms; - -global using LocalModels; - -global using Management.Planner; -global using Management.Services; -global using Management.Services.Canvas; -global using Management.Web.Shared; -global using Management.Web.Shared.Components; - -using dotenv.net; - -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Hosting.Server.Features; -using Microsoft.AspNetCore.ResponseCompression; - -using OpenTelemetry; -using OpenTelemetry.Logs; -using OpenTelemetry.Metrics; -using OpenTelemetry.Resources; -using OpenTelemetry.Trace; - -DotEnv.Load(); - -var builder = WebApplication.CreateBuilder(args); - -ConfigurationSetup.Canvas(builder); - -const string serviceName = "canvas-management"; - -builder.Logging.AddOpenTelemetry(options => -{ - options - .SetResourceBuilder( - ResourceBuilder - .CreateDefault() - .AddService(serviceName) - ) - .AddOtlpExporter(o => - { - o.Endpoint = new Uri("http://localhost:4317/"); - }); - // .AddConsoleExporter(); -}); - -builder.Services.AddOpenTelemetry() - .ConfigureResource(resource => resource.AddService(serviceName)) - .WithTracing(tracing => tracing - .AddSource(DiagnosticsConfig.SourceName) - .AddOtlpExporter(o => - { - o.Endpoint = new Uri("http://localhost:4317/"); - }) - .AddAspNetCoreInstrumentation() - .AddProcessor(new BatchActivityExportProcessor(new CustomConsoleExporter())) - ) - .WithMetrics(metrics => metrics - .AddOtlpExporter(o => - { - o.Endpoint = new Uri("http://localhost:4317/"); - } - ) - .AddAspNetCoreInstrumentation() -); - -// Add services to the container. -builder.Services.AddRazorPages(); -builder.Services.AddServerSideBlazor(); - -builder.Services.AddLogging(); - -builder.Services.AddSingleton(typeof(MyLogger<>)); - -// stateless services -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); - -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); - -builder.Services.AddSingleton(); - -// one actor system, maybe different actor for different pages? -builder.Services.AddSingleton(); -builder.Services.AddHostedService(sp => sp.GetRequiredService()); - - -// TODO: need to handle scoped requirements -// builder.Services.AddSingleton(sp => -// { -// var akka = sp.GetRequiredService(); -// return new CanvasQueueActorWrapper(akka.CoursePlannerActor ?? throw new Exception("Canvas queue actor not properly created")); -// }); -builder.Services.AddSingleton(sp => -{ - var akka = sp.GetRequiredService(); - return new LocalStorageActorWrapper(akka.StorageActor ?? throw new Exception("Canvas queue actor not properly created")); -}); - - -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - -builder.Services.AddSingleton(); - -builder.Services.AddResponseCompression(opts => -{ - opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/octet-stream" }); -}); - -builder.Services.AddSignalR(e => -{ - e.MaximumReceiveMessageSize = 102400000; -}); - - - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); -} - -app.UseHttpsRedirection(); - -app.UseStaticFiles(); - -app.UseRouting(); -// app.UseResponseCompression(); - -app.MapBlazorHub(); -app.MapFallbackToPage("/_Host"); - - -app.Start(); - -var addresses = app.Services.GetService()?.Features.Get()?.Addresses ?? []; - -foreach (var address in addresses) -{ - Console.WriteLine("Running at: " + address); -} - -app.WaitForShutdown(); - diff --git a/Management.Web/Properties/launchSettings.json b/Management.Web/Properties/launchSettings.json deleted file mode 100644 index f9766b4..0000000 --- a/Management.Web/Properties/launchSettings.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:25470", - "sslPort": 44349 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5087", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:7055;http://localhost:5087", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/Management.Web/Shared/Components/CheckIcon.razor b/Management.Web/Shared/Components/CheckIcon.razor deleted file mode 100644 index 28e1c0c..0000000 --- a/Management.Web/Shared/Components/CheckIcon.razor +++ /dev/null @@ -1,20 +0,0 @@ - - - - \ No newline at end of file diff --git a/Management.Web/Shared/Components/ConfirmationModal.razor b/Management.Web/Shared/Components/ConfirmationModal.razor deleted file mode 100644 index f58721f..0000000 --- a/Management.Web/Shared/Components/ConfirmationModal.razor +++ /dev/null @@ -1,85 +0,0 @@ -@namespace Management.Web.Shared.Components - -@code { - [Parameter] - public Action? OnConfirm { get; init; } - [Parameter] - public Action? OnDeny { get; init; } - [Parameter] - public Func? OnConfirmAsync { get; init; } - [Parameter] - public Func? OnDenyAsync { get; init; } - [Parameter] - [EditorRequired] - public string Label { get; set; } = ""; - [Parameter] - [EditorRequired] - public string Class { get; set; } = ""; - [Parameter] - public bool Disabled { get; set; } = false; - - private Modal? modal { get; set; } = null; - - private bool doingAsyncThings { get; set; } = false; - - private async Task HandleDeny() - { - if(OnDeny != null) - OnDeny(); - if(OnDenyAsync != null) - { - doingAsyncThings = true; - await OnDenyAsync(); - doingAsyncThings = false; - } - modal?.Hide(); - } - private async Task HandleConfirm() - { - if(OnConfirm != null) - OnConfirm(); - if(OnConfirmAsync != null) - { - doingAsyncThings = true; - await OnConfirmAsync(); - doingAsyncThings = false; - } - modal?.Hide(); - } -} - - - - - Are you sure you want to @Label? - -
    - - -
    - -
    - @if(doingAsyncThings) - { - - } -
    -
    \ No newline at end of file diff --git a/Management.Web/Shared/Components/Forms/ButtonSelect.razor b/Management.Web/Shared/Components/Forms/ButtonSelect.razor deleted file mode 100644 index 8dbf30d..0000000 --- a/Management.Web/Shared/Components/Forms/ButtonSelect.razor +++ /dev/null @@ -1,43 +0,0 @@ - -@typeparam T - -@code { - [Parameter, EditorRequired] - public string Label { get; set; } = string.Empty; - [Parameter, EditorRequired] - public IEnumerable Options { get; set; } = default!; - [Parameter, EditorRequired] - public Func GetName { get; set; } = default!; - - [Parameter, EditorRequired] - public Action OnSelect { get; set; } = default!; - - [Parameter] - public T? SelectedOption { get; set; } - - private string htmlLabel => Label.Replace("-", ""); - - private void onSelect(T option) - { - SelectedOption = option; - OnSelect(SelectedOption); - } - - private string getButtonClass(T option) - { - var partClass = GetName(option) == GetName(SelectedOption) ? "primary" : "outline-primary"; - return $"mx-1 btn btn-{partClass}"; - } -} - -
    - @foreach(var option in Options) - { - - } -
    \ No newline at end of file diff --git a/Management.Web/Shared/Components/Forms/FormSelect.razor b/Management.Web/Shared/Components/Forms/FormSelect.razor deleted file mode 100644 index d36ca24..0000000 --- a/Management.Web/Shared/Components/Forms/FormSelect.razor +++ /dev/null @@ -1,45 +0,0 @@ - -@typeparam T - -@code { - [Parameter, EditorRequired] - public string Label { get; set; } = string.Empty; - [Parameter, EditorRequired] - public IEnumerable Options { get; set; } = default!; - - [Parameter, EditorRequired] - public Func GetId { get; set; } = default!; - - [Parameter, EditorRequired] - public Func GetName { get; set; } = default!; - - [Parameter, EditorRequired] - public Action OnSelect { get; set; } = default!; - - private string htmlLabel => Label.Replace("-", ""); - - private void onSelect(ChangeEventArgs e) - { - var newId = e.Value?.ToString(); - var selectedOption = Options.FirstOrDefault(o => GetId(o) == newId); - OnSelect(selectedOption); - } -} - -
    - - -
    \ No newline at end of file diff --git a/Management.Web/Shared/Components/MeatballsIcon.razor b/Management.Web/Shared/Components/MeatballsIcon.razor deleted file mode 100644 index eb8ee13..0000000 --- a/Management.Web/Shared/Components/MeatballsIcon.razor +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/Management.Web/Shared/Components/Modal.razor b/Management.Web/Shared/Components/Modal.razor deleted file mode 100644 index ba1cc0c..0000000 --- a/Management.Web/Shared/Components/Modal.razor +++ /dev/null @@ -1,56 +0,0 @@ -@code { - [Parameter, EditorRequired] - public RenderFragment? Title { get; set; } - - [Parameter, EditorRequired] - public RenderFragment? Body { get; set; } - - [Parameter, EditorRequired] - public RenderFragment? Footer { get; set; } - - [Parameter] - public Action OnShow { get; set; } = () => { }; - - [Parameter] - public Action OnHide { get; set; } = () => { }; - - [Parameter] - public string Size { get; set; } = "xl"; //sm, lg, xl, xxl - - private string modalClass = "hide-modal"; - private bool showBackdrop = false; - public void Show() - { - - modalClass = "show-modal"; - showBackdrop = true; - OnShow(); - } - - public void Hide() - { - modalClass = "hide-modal"; - showBackdrop = false; - OnHide(); - } -} - - - -@if (showBackdrop) -{ - -} diff --git a/Management.Web/Shared/Components/Modal.razor.css b/Management.Web/Shared/Components/Modal.razor.css deleted file mode 100644 index 46eda54..0000000 --- a/Management.Web/Shared/Components/Modal.razor.css +++ /dev/null @@ -1,16 +0,0 @@ - -.show-modal { - animation: enter 250ms; - display: block; - opacity: 1; -} - -@keyframes enter { - from { - display: block; - opacity: 0; - } - to { - opacity: 1; - } -} diff --git a/Management.Web/Shared/Components/MonacoEditorDemo.razor b/Management.Web/Shared/Components/MonacoEditorDemo.razor deleted file mode 100644 index 394387a..0000000 --- a/Management.Web/Shared/Components/MonacoEditorDemo.razor +++ /dev/null @@ -1,84 +0,0 @@ - -@using BlazorMonaco -@using BlazorMonaco.Editor - -

    Code Editor

    - -
    -
    - New Value: -
    -
    - -
    -
    - See the console for results. -
    -
    - -
    - -
    - -@code { - private StandaloneCodeEditor _editor = null!; - private string _valueToSet = ""; - - private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor) - { - return new StandaloneEditorConstructionOptions - { - Language = "markdown", - Theme = "vs-dark", - TabSize = 2, - Value = "this is the default \n value", - Minimap = new EditorMinimapOptions { Enabled = false } - }; - } - - private async Task EditorOnDidInit() - { - await _editor.AddCommand((int)KeyMod.CtrlCmd | (int)KeyCode.KeyH, (args) => - { - Console.WriteLine("Ctrl+H : Initial editor command is triggered."); - }); - - var newDecorations = new ModelDeltaDecoration[] - { - new ModelDeltaDecoration - { - Range = new BlazorMonaco.Range(3,1,3,1), - Options = new ModelDecorationOptions - { - IsWholeLine = true, - ClassName = "decorationContentClass", - GlyphMarginClassName = "decorationGlyphMarginClass" - } - } - }; - - decorationIds = await _editor.DeltaDecorations(null, newDecorations); - // You can now use 'decorationIds' to change or remove the decorations - } - - private string[] decorationIds = new string[0]; - - private async Task SetValue() - { - Console.WriteLine($"setting value to: {_valueToSet}"); - await _editor.SetValue(_valueToSet); - } - - private async Task GetValue() - { - var val = await _editor.GetValue(); - Console.WriteLine($"value is: {val}"); - } -} \ No newline at end of file diff --git a/Management.Web/Shared/Components/MonacoTextArea.razor b/Management.Web/Shared/Components/MonacoTextArea.razor deleted file mode 100644 index 26918ac..0000000 --- a/Management.Web/Shared/Components/MonacoTextArea.razor +++ /dev/null @@ -1,59 +0,0 @@ -@* @rendermode @(new InteractiveServerRenderMode(prerender: false)) *@ -@implements IDisposable -@using BlazorMonaco -@using BlazorMonaco.Editor - -@code { - [Parameter, EditorRequired] - public string Value { get; set; } = default!; - - [Parameter, EditorRequired] - public Action OnChange { get; set; } = default!; - - - private string randomId = "monaco-editor-" + BitConverter.ToString(new byte[16].Select(b => (byte)new - Random().Next(256)).ToArray()).Replace("-", ""); - - - private StandaloneCodeEditor? _editor = null; - - private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor) - { - return new StandaloneEditorConstructionOptions - { - Language = "markdown", - Theme = "vs-dark", - TabSize = 2, - Value = Value, - Minimap = new EditorMinimapOptions { Enabled = false }, - LineNumbers = "off", - LineDecorationsWidth = 0, - WordWrap = "on", - AutomaticLayout = true, - FontFamily = "Roboto-mono", - FontSize = 16, - Padding = new EditorPaddingOptions() - { - Top = 10 - } - }; - } - - private async Task OnDidChangeModelContent() - { - if (_editor == null) return; - var newValue = await _editor.GetValue(); - OnChange(newValue); - } - - void IDisposable.Dispose() - { - _editor?.Dispose(); - _editor = null; - } -} - - - - diff --git a/Management.Web/Shared/Components/Spinner.razor b/Management.Web/Shared/Components/Spinner.razor deleted file mode 100644 index 93cdbd2..0000000 --- a/Management.Web/Shared/Components/Spinner.razor +++ /dev/null @@ -1,4 +0,0 @@ - -
    - -
    \ No newline at end of file diff --git a/Management.Web/Shared/Components/Spinner.razor.css b/Management.Web/Shared/Components/Spinner.razor.css deleted file mode 100644 index c0d6049..0000000 --- a/Management.Web/Shared/Components/Spinner.razor.css +++ /dev/null @@ -1,56 +0,0 @@ -.loader { - width: 48px; - height: 48px; - border-radius: 50%; - display: inline-block; - position: relative; - border: 3px solid; - border-color: #6c757d #6c757d transparent transparent; - box-sizing: border-box; - animation: rotation 2s linear infinite; -} -.loader::after, -.loader::before { - content: ''; - box-sizing: border-box; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - margin: auto; - border: 3px solid; - border-color: transparent transparent #092565 #092565; - width: 40px; - height: 40px; - border-radius: 50%; - box-sizing: border-box; - animation: rotationBack 1s linear infinite; - transform-origin: center center; -} -/* #092565 */ -/* #3a0647 */ -.loader::before { - width: 32px; - height: 32px; - border-color: #6c757d #6c757d transparent transparent; - animation: rotation 3s linear infinite; -} - -@keyframes rotation { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} -@keyframes rotationBack { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(-360deg); - } -} - \ No newline at end of file diff --git a/Management.Web/Shared/Components/SyncIcon.razor b/Management.Web/Shared/Components/SyncIcon.razor deleted file mode 100644 index cac5057..0000000 --- a/Management.Web/Shared/Components/SyncIcon.razor +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/Management.Web/Shared/Components/TimePicker.razor b/Management.Web/Shared/Components/TimePicker.razor deleted file mode 100644 index 5e76040..0000000 --- a/Management.Web/Shared/Components/TimePicker.razor +++ /dev/null @@ -1,127 +0,0 @@ - -@inject CoursePlanner planner - -@code { - [Parameter] - [EditorRequired] - public SimpleTimeOnly Time { get; set; } = default!; - - [Parameter] - [EditorRequired] - public Action UpdateTime { get; set; }= default!; - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } - - private string AmPm - { - get => Time.Hour < 12 ? "AM" : "PM"; - } - - private int AdjustedHour - { - get - { - var time = Time.Hour % 12; - if (time == 0) return 12; - return time; - } - } - - private int convertTo24Hour(int hour, string? amPm) - { - if(amPm == "AM") - { - return hour % 12; - } - else - { - if (hour == 12) - return 12; - else - return hour + 12; - } - } -} -
    - -: - - - -
    \ No newline at end of file diff --git a/Management.Web/Shared/Components/ValidateCanvasToken.razor b/Management.Web/Shared/Components/ValidateCanvasToken.razor deleted file mode 100644 index ab60144..0000000 --- a/Management.Web/Shared/Components/ValidateCanvasToken.razor +++ /dev/null @@ -1,49 +0,0 @@ - -@code -{ - [Parameter, EditorRequired] - public Func SetToken { get; set; } = default!; - private Modal modal { get; set; } = default!; - private string tokenInput { get; set; } = ""; - - protected override void OnAfterRender(bool firstRender) - { - if(firstRender) - modal.Show(); - } - - -} - - - - <h3>Canvas Token</h3> - - -
    -

    - Please input your canvas token to enable canvas integration -

    -

    - We only store the token encrypted in your browser. We do not store the token on our servers. -

    -

    - You can get your canvas token here -

    -
    - -
    -
    - -
    -
    -
    - diff --git a/Management.Web/Shared/CurrentFiles.razor b/Management.Web/Shared/CurrentFiles.razor deleted file mode 100644 index 366c51c..0000000 --- a/Management.Web/Shared/CurrentFiles.razor +++ /dev/null @@ -1,39 +0,0 @@ -@using LocalModels - -@inject IFileStorageManager fileStorageManager -@inject CoursePlanner planner -@inject NavigationManager Navigation -@inject MyLogger logger - -@code -{ - [Parameter] - public int RefreshKey { get; set; } - public IEnumerable? localCourses { get; set; } - protected override async Task OnParametersSetAsync() - { - localCourses = await fileStorageManager.LoadSavedCourses(); - } - - void handleClick(MouseEventArgs e, LocalCourse course) - { - planner.LocalCourse = course; - Navigation.NavigateTo("/course/" + course.Settings.Name); - } -} - -
    - @if (localCourses != null) - { -

    Stored Courses

    - @foreach (var course in localCourses) - { - var location = "/course/" + course.Settings.Name; -
    -
    - @course.Settings.Name -
    -
    - } - } -
    diff --git a/Management.Web/Shared/CurrentFiles.razor.css b/Management.Web/Shared/CurrentFiles.razor.css deleted file mode 100644 index bb8e7bf..0000000 --- a/Management.Web/Shared/CurrentFiles.razor.css +++ /dev/null @@ -1,30 +0,0 @@ -.hover-underline-animation { - display: inline-block; - position: relative; - color: var(--bs-primary-text-emphasis); - transition: all 500ms; -} - -.hover-underline-animation:hover { - /* text-shadow: 10px 10px #092565; */ - /* text-shadow: 10px 10px 40px #092565; */ - transform: scale(1.05); -} - -.hover-underline-animation::after { - content: ''; - position: absolute; - width: 100%; - transform: scaleX(0); - height: 2px; - bottom: 0; - left: 0; - background-color: var(--bs-primary-text-emphasis); - transform-origin: bottom right; - transition: transform 500ms ease-out; -} - -.hover-underline-animation:hover::after { - transform: scaleX(1); - transform-origin: bottom left; -} \ No newline at end of file diff --git a/Management.Web/Shared/InitializeNewCourse.razor b/Management.Web/Shared/InitializeNewCourse.razor deleted file mode 100644 index 43001ae..0000000 --- a/Management.Web/Shared/InitializeNewCourse.razor +++ /dev/null @@ -1,196 +0,0 @@ -@using CanvasModel.EnrollmentTerms -@using Management.Web.Shared.Components -@using CanvasModel.Courses -@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@using LocalModels - -@inject ICanvasService canvas -@inject IFileStorageManager fileStorageManager - - -@code { - [Parameter, EditorRequired] - public Action NewFileCreated { get; set; } = default!; - private bool loadingTerms = false; - private bool loadingCourses = false; - public IEnumerable? localCourses { get; set; } - private IEnumerable? terms { get; set; } = null; - private IEnumerable? courses { get; set; } = null; - private ulong? _selectedTermId { get; set; } - private ulong? selectedTermId - { - get => _selectedTermId; - set - { - _selectedTermId = value; - this.InvokeAsync(updateCourses); - } - } - - private EnrollmentTermModel? selectedTerm - { - get => terms?.FirstOrDefault(t => t.Id == selectedTermId); - } - - private ulong? _selectedCourseId { get; set; } - private ulong? selectedCourseId - { - get => _selectedCourseId; - set - { - _selectedCourseId = value; - } - } - - private CourseModel? selectedCourse - { - get => courses?.First(c => c.Id == selectedCourseId); - } - - private List days { get; set; } = new(); - - private IEnumerable directoriesNotUsed { get; set; } = []; - private string? selectedStorageDirectory { get; set; } = null; - protected override async Task OnInitializedAsync() - { - loadingTerms = true; - terms = await canvas.GetCurrentTermsFor(); - loadingTerms = false; - directoriesNotUsed = await fileStorageManager.GetEmptyDirectories(); - } - - private async Task SaveNewCourse() - { - if (selectedCourse != null && selectedStorageDirectory != null && selectedStorageDirectory != string.Empty) - { - var course = new LocalCourse - { - Modules = new LocalModule[] { }, - Settings = new LocalCourseSettings() - { - Name = Path.GetFileName(selectedStorageDirectory), - CanvasId = selectedCourse.Id, - StartDate = selectedTerm?.StartAt ?? new DateTime(), - EndDate = selectedTerm?.EndAt ?? new DateTime(), - DaysOfWeek = days, - } - }; - await fileStorageManager.SaveCourseAsync(course, null); - NewFileCreated(); - } - await updateCourses(); - } - - private async Task updateCourses() - { - if (selectedTermId != null) - { - loadingCourses = true; - - localCourses = await fileStorageManager.LoadSavedCourses(); - var storedCourseIds = localCourses.Select(c => c.Settings.CanvasId); - var allCourses = await canvas.GetCourses((ulong)selectedTermId); - courses = allCourses.Where(c => !storedCourseIds.Contains(c.Id)); - loadingCourses = false; - } - else - courses = null; - - StateHasChanged(); - } -} - -@if (loadingTerms) -{ - -} - -@if (terms != null) -{ -
    -
    - - -
    -
    -} - - -@if (selectedTerm is not null) -{ - @if (loadingCourses) - { - - } - - @if (courses != null) - { -
    -
    - - -
    -
    -
    -
    - - -
    -
    - } - -
    Select Days Of Week
    -
    - @foreach (DayOfWeek day in (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek))) - { -
    - -
    - } -
    - - -
    - -
    -} diff --git a/Management.Web/Shared/MainLayout.razor b/Management.Web/Shared/MainLayout.razor deleted file mode 100644 index 4bb4815..0000000 --- a/Management.Web/Shared/MainLayout.razor +++ /dev/null @@ -1,10 +0,0 @@ -@inherits LayoutComponentBase - -Management.Web - - -
    -
    - @Body -
    -
    diff --git a/Management.Web/Utils/AssignmentDragContainer.cs b/Management.Web/Utils/AssignmentDragContainer.cs deleted file mode 100644 index 60f0c45..0000000 --- a/Management.Web/Utils/AssignmentDragContainer.cs +++ /dev/null @@ -1,4 +0,0 @@ -public class DragContainer -{ - public Action? DropCallback { get; set; } -} diff --git a/Management.Web/_Imports.razor b/Management.Web/_Imports.razor deleted file mode 100644 index fef9935..0000000 --- a/Management.Web/_Imports.razor +++ /dev/null @@ -1,11 +0,0 @@ -@using System.Net.Http -@using Microsoft.AspNetCore.Authorization -@using Microsoft.AspNetCore.Components.Authorization -@using Microsoft.AspNetCore.Components.Forms -@using Microsoft.AspNetCore.Components.Routing -@using Microsoft.AspNetCore.Components.Web -@using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.JSInterop -@using Management.Web -@using Management.Web.Shared -@using static Microsoft.AspNetCore.Components.Web.RenderMode diff --git a/Management.Web/appsettings.Development.json b/Management.Web/appsettings.Development.json deleted file mode 100644 index 253cd2d..0000000 --- a/Management.Web/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "DetailedErrors": true, - "Logging": { - "LogLevel": { - "Default": "Error", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/Management.Web/appsettings.json b/Management.Web/appsettings.json deleted file mode 100644 index 04986a1..0000000 --- a/Management.Web/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Error", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/Management.Web/wwwroot/css/bootstrap/bootstrap.min.css b/Management.Web/wwwroot/css/bootstrap/bootstrap.min.css deleted file mode 100644 index ae117a2..0000000 --- a/Management.Web/wwwroot/css/bootstrap/bootstrap.min.css +++ /dev/null @@ -1,11 +0,0 @@ -@charset "UTF-8";/*! - * Bootswatch v5.3.0 (https://bootswatch.com) - * Theme: pulse - * Copyright 2012-2023 Thomas Park - * Licensed under MIT - * Based on Bootstrap -*//*! - * Bootstrap v5.3.0 (https://getbootstrap.com/) - * Copyright 2011-2023 The Bootstrap Authors - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root,[data-bs-theme=light]{--bs-blue:#007bff;--bs-indigo:#6610f2;--bs-purple:#593196;--bs-pink:#e83e8c;--bs-red:#fc3939;--bs-orange:#fd7e14;--bs-yellow:#efa31d;--bs-green:#13b955;--bs-teal:#20c997;--bs-cyan:#009cdc;--bs-black:#000;--bs-white:#fff;--bs-gray:#868e96;--bs-gray-dark:#343a40;--bs-gray-100:#fafafa;--bs-gray-200:#f9f8fc;--bs-gray-300:#ededed;--bs-gray-400:#cbc8d0;--bs-gray-500:#adb5bd;--bs-gray-600:#868e96;--bs-gray-700:#444;--bs-gray-800:#343a40;--bs-gray-900:#17141f;--bs-primary:#593196;--bs-secondary:#a991d4;--bs-success:#13b955;--bs-info:#009cdc;--bs-warning:#efa31d;--bs-danger:#fc3939;--bs-light:#f9f8fc;--bs-dark:#17141f;--bs-primary-rgb:89,49,150;--bs-secondary-rgb:169,145,212;--bs-success-rgb:19,185,85;--bs-info-rgb:0,156,220;--bs-warning-rgb:239,163,29;--bs-danger-rgb:252,57,57;--bs-light-rgb:249,248,252;--bs-dark-rgb:23,20,31;--bs-primary-text-emphasis:#24143c;--bs-secondary-text-emphasis:#443a55;--bs-success-text-emphasis:#084a22;--bs-info-text-emphasis:#003e58;--bs-warning-text-emphasis:#60410c;--bs-danger-text-emphasis:#651717;--bs-light-text-emphasis:#444;--bs-dark-text-emphasis:#444;--bs-primary-bg-subtle:#ded6ea;--bs-secondary-bg-subtle:#eee9f6;--bs-success-bg-subtle:#d0f1dd;--bs-info-bg-subtle:#ccebf8;--bs-warning-bg-subtle:#fcedd2;--bs-danger-bg-subtle:#fed7d7;--bs-light-bg-subtle:#fdfdfd;--bs-dark-bg-subtle:#cbc8d0;--bs-primary-border-subtle:#bdadd5;--bs-secondary-border-subtle:#ddd3ee;--bs-success-border-subtle:#a1e3bb;--bs-info-border-subtle:#99d7f1;--bs-warning-border-subtle:#f9daa5;--bs-danger-border-subtle:#feb0b0;--bs-light-border-subtle:#f9f8fc;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#444;--bs-body-color-rgb:68,68,68;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(68, 68, 68, 0.75);--bs-secondary-color-rgb:68,68,68;--bs-secondary-bg:#f9f8fc;--bs-secondary-bg-rgb:249,248,252;--bs-tertiary-color:rgba(68, 68, 68, 0.5);--bs-tertiary-color-rgb:68,68,68;--bs-tertiary-bg:#fafafa;--bs-tertiary-bg-rgb:250,250,250;--bs-heading-color:inherit;--bs-link-color:#593196;--bs-link-color-rgb:89,49,150;--bs-link-decoration:underline;--bs-link-hover-color:#593196;--bs-link-hover-color-rgb:89,49,150;--bs-code-color:#e83e8c;--bs-highlight-bg:#fcedd2;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#ededed;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(89, 49, 150, 0.25);--bs-form-valid-color:#13b955;--bs-form-valid-border-color:#13b955;--bs-form-invalid-color:#fc3939;--bs-form-invalid-border-color:#fc3939}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#adb5bd;--bs-body-color-rgb:173,181,189;--bs-body-bg:#17141f;--bs-body-bg-rgb:23,20,31;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(173, 181, 189, 0.75);--bs-secondary-color-rgb:173,181,189;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(173, 181, 189, 0.5);--bs-tertiary-color-rgb:173,181,189;--bs-tertiary-bg:#262730;--bs-tertiary-bg-rgb:38,39,48;--bs-primary-text-emphasis:#9b83c0;--bs-secondary-text-emphasis:#cbbde5;--bs-success-text-emphasis:#71d599;--bs-info-text-emphasis:#66c4ea;--bs-warning-text-emphasis:#f5c877;--bs-danger-text-emphasis:#fd8888;--bs-light-text-emphasis:#fafafa;--bs-dark-text-emphasis:#ededed;--bs-primary-bg-subtle:#120a1e;--bs-secondary-bg-subtle:#221d2a;--bs-success-bg-subtle:#042511;--bs-info-bg-subtle:#001f2c;--bs-warning-bg-subtle:#302106;--bs-danger-bg-subtle:#320b0b;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#351d5a;--bs-secondary-border-subtle:#65577f;--bs-success-border-subtle:#0b6f33;--bs-info-border-subtle:#005e84;--bs-warning-border-subtle:#8f6211;--bs-danger-border-subtle:#972222;--bs-light-border-subtle:#444;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#9b83c0;--bs-link-hover-color:#af9ccd;--bs-link-color-rgb:155,131,192;--bs-link-hover-color-rgb:175,156,205;--bs-code-color:#f18bba;--bs-border-color:#444;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#71d599;--bs-form-valid-border-color:#71d599;--bs-form-invalid-color:#fd8888;--bs-form-invalid-border-color:#fd8888}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color)}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#868e96}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-body-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:rgba(0, 0, 0, 0.05);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-body-color);--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:var(--bs-body-color);--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:var(--bs-body-color);--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#ded6ea;--bs-table-border-color:#c8c1d3;--bs-table-striped-bg:#d3cbde;--bs-table-striped-color:#000;--bs-table-active-bg:#c8c1d3;--bs-table-active-color:#000;--bs-table-hover-bg:#cdc6d8;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#eee9f6;--bs-table-border-color:#d6d2dd;--bs-table-striped-bg:#e2ddea;--bs-table-striped-color:#000;--bs-table-active-bg:#d6d2dd;--bs-table-active-color:#000;--bs-table-hover-bg:#dcd8e4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d0f1dd;--bs-table-border-color:#bbd9c7;--bs-table-striped-bg:#c6e5d2;--bs-table-striped-color:#000;--bs-table-active-bg:#bbd9c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c0dfcc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#ccebf8;--bs-table-border-color:#b8d4df;--bs-table-striped-bg:#c2dfec;--bs-table-striped-color:#000;--bs-table-active-bg:#b8d4df;--bs-table-active-color:#000;--bs-table-hover-bg:#bdd9e5;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fcedd2;--bs-table-border-color:#e3d5bd;--bs-table-striped-bg:#efe1c8;--bs-table-striped-color:#000;--bs-table-active-bg:#e3d5bd;--bs-table-active-color:#000;--bs-table-hover-bg:#e9dbc2;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#fed7d7;--bs-table-border-color:#e5c2c2;--bs-table-striped-bg:#f1cccc;--bs-table-striped-color:#000;--bs-table-active-bg:#e5c2c2;--bs-table-active-color:#000;--bs-table-hover-bg:#ebc7c7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f9f8fc;--bs-table-border-color:#e0dfe3;--bs-table-striped-bg:#edecef;--bs-table-striped-color:#000;--bs-table-active-bg:#e0dfe3;--bs-table-active-color:#000;--bs-table-hover-bg:#e6e5e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#17141f;--bs-table-border-color:#2e2c35;--bs-table-striped-bg:#23202a;--bs-table-striped-color:#fff;--bs-table-active-bg:#2e2c35;--bs-table-active-color:#fff;--bs-table-hover-bg:#282630;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid var(--bs-border-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#593196;outline:0;box-shadow:0 0 0 .25rem rgba(89,49,150,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:transparent;border:solid transparent;border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:.875rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important}.form-control-color::-webkit-color-swatch{border:0!important}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#593196;outline:0;box-shadow:0 0 0 .25rem rgba(89,49,150,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23adb5bd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#593196;outline:0;box-shadow:0 0 0 .25rem rgba(89,49,150,.25)}.form-check-input:checked{background-color:#593196;border-color:#593196}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#593196;border-color:#593196;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23593196'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(89,49,150,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(89,49,150,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#593196;border:0;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#cdc1e0}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-tertiary-bg);border-color:transparent}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#593196;border:0;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#cdc1e0}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-tertiary-bg);border-color:transparent}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control-plaintext::-moz-placeholder,.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:not(:-moz-placeholder-shown),.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:not(:-moz-placeholder-shown)~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg)}.form-floating>.form-control-plaintext~label::after,.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg)}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>:disabled~label{color:#868e96}.form-floating>:disabled~label::after{background-color:var(--bs-secondary-bg)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color)}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(var(--bs-border-width) * -1)}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success)}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2313b955' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2313b955' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger)}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23fc3939'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23fc3939' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23fc3939'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23fc3939' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:0.75rem;--bs-btn-padding-y:0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.15),0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#593196;--bs-btn-border-color:#593196;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#4c2a80;--bs-btn-hover-border-color:#472778;--bs-btn-focus-shadow-rgb:114,80,166;--bs-btn-active-color:#fff;--bs-btn-active-bg:#472778;--bs-btn-active-border-color:#432571;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#593196;--bs-btn-disabled-border-color:#593196}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#a991d4;--bs-btn-border-color:#a991d4;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#907bb4;--bs-btn-hover-border-color:#8774aa;--bs-btn-focus-shadow-rgb:182,162,218;--bs-btn-active-color:#fff;--bs-btn-active-bg:#8774aa;--bs-btn-active-border-color:#7f6d9f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#a991d4;--bs-btn-disabled-border-color:#a991d4}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#13b955;--bs-btn-border-color:#13b955;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#109d48;--bs-btn-hover-border-color:#0f9444;--bs-btn-focus-shadow-rgb:54,196,111;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0f9444;--bs-btn-active-border-color:#0e8b40;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#13b955;--bs-btn-disabled-border-color:#13b955}.btn-info{--bs-btn-color:#fff;--bs-btn-bg:#009cdc;--bs-btn-border-color:#009cdc;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0085bb;--bs-btn-hover-border-color:#007db0;--bs-btn-focus-shadow-rgb:38,171,225;--bs-btn-active-color:#fff;--bs-btn-active-bg:#007db0;--bs-btn-active-border-color:#0075a5;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#009cdc;--bs-btn-disabled-border-color:#009cdc}.btn-warning{--bs-btn-color:#fff;--bs-btn-bg:#efa31d;--bs-btn-border-color:#efa31d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#cb8b19;--bs-btn-hover-border-color:#bf8217;--bs-btn-focus-shadow-rgb:241,177,63;--bs-btn-active-color:#fff;--bs-btn-active-bg:#bf8217;--bs-btn-active-border-color:#b37a16;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#efa31d;--bs-btn-disabled-border-color:#efa31d}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#fc3939;--bs-btn-border-color:#fc3939;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#d63030;--bs-btn-hover-border-color:#ca2e2e;--bs-btn-focus-shadow-rgb:252,87,87;--bs-btn-active-color:#fff;--bs-btn-active-bg:#ca2e2e;--bs-btn-active-border-color:#bd2b2b;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#fc3939;--bs-btn-disabled-border-color:#fc3939}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f9f8fc;--bs-btn-border-color:#f9f8fc;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d4d3d6;--bs-btn-hover-border-color:#c7c6ca;--bs-btn-focus-shadow-rgb:212,211,214;--bs-btn-active-color:#000;--bs-btn-active-bg:#c7c6ca;--bs-btn-active-border-color:#bbbabd;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f9f8fc;--bs-btn-disabled-border-color:#f9f8fc}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#17141f;--bs-btn-border-color:#17141f;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#3a3741;--bs-btn-hover-border-color:#2e2c35;--bs-btn-focus-shadow-rgb:58,55,65;--bs-btn-active-color:#fff;--bs-btn-active-bg:#45434c;--bs-btn-active-border-color:#2e2c35;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#17141f;--bs-btn-disabled-border-color:#17141f}.btn-outline-primary{--bs-btn-color:#593196;--bs-btn-border-color:#593196;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#593196;--bs-btn-hover-border-color:#593196;--bs-btn-focus-shadow-rgb:89,49,150;--bs-btn-active-color:#fff;--bs-btn-active-bg:#593196;--bs-btn-active-border-color:#593196;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#593196;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#593196;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#a991d4;--bs-btn-border-color:#a991d4;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#a991d4;--bs-btn-hover-border-color:#a991d4;--bs-btn-focus-shadow-rgb:169,145,212;--bs-btn-active-color:#fff;--bs-btn-active-bg:#a991d4;--bs-btn-active-border-color:#a991d4;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#a991d4;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#a991d4;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#13b955;--bs-btn-border-color:#13b955;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#13b955;--bs-btn-hover-border-color:#13b955;--bs-btn-focus-shadow-rgb:19,185,85;--bs-btn-active-color:#fff;--bs-btn-active-bg:#13b955;--bs-btn-active-border-color:#13b955;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#13b955;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#13b955;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#009cdc;--bs-btn-border-color:#009cdc;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#009cdc;--bs-btn-hover-border-color:#009cdc;--bs-btn-focus-shadow-rgb:0,156,220;--bs-btn-active-color:#fff;--bs-btn-active-bg:#009cdc;--bs-btn-active-border-color:#009cdc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#009cdc;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#009cdc;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#efa31d;--bs-btn-border-color:#efa31d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#efa31d;--bs-btn-hover-border-color:#efa31d;--bs-btn-focus-shadow-rgb:239,163,29;--bs-btn-active-color:#fff;--bs-btn-active-bg:#efa31d;--bs-btn-active-border-color:#efa31d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#efa31d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#efa31d;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#fc3939;--bs-btn-border-color:#fc3939;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#fc3939;--bs-btn-hover-border-color:#fc3939;--bs-btn-focus-shadow-rgb:252,57,57;--bs-btn-active-color:#fff;--bs-btn-active-bg:#fc3939;--bs-btn-active-border-color:#fc3939;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fc3939;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#fc3939;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f9f8fc;--bs-btn-border-color:#f9f8fc;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f9f8fc;--bs-btn-hover-border-color:#f9f8fc;--bs-btn-focus-shadow-rgb:249,248,252;--bs-btn-active-color:#000;--bs-btn-active-bg:#f9f8fc;--bs-btn-active-border-color:#f9f8fc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#f9f8fc;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f9f8fc;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#17141f;--bs-btn-border-color:#17141f;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#17141f;--bs-btn-hover-border-color:#17141f;--bs-btn-focus-shadow-rgb:23,20,31;--bs-btn-active-color:#fff;--bs-btn-active-bg:#17141f;--bs-btn-active-border-color:#17141f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#17141f;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#17141f;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#868e96;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:114,80,166;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:0.5rem;--bs-dropdown-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-dropdown-link-color:#444;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-link-hover-bg:#593196;--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#593196;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:0.25rem;--bs-dropdown-header-color:#868e96;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#ededed;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#ededed;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#593196;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:calc(var(--bs-border-width) * -1)}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:calc(var(--bs-border-width) * -1)}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;background:0 0;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(89,49,150,.25)}.nav-link.disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:#ededed;--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:#593196;--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.disabled,.nav-tabs .nav-link:disabled{color:var(--bs-nav-link-disabled-color);background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width))}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#593196}.nav-pills .nav-link:disabled{color:var(--bs-nav-link-disabled-color);background-color:transparent;border-color:transparent}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid transparent}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:1.2rem;--bs-navbar-color:rgba(0, 0, 0, 0.4);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color:rgba(0, 0, 0, 0.2);--bs-navbar-active-color:rgba(0, 0, 0, 0.7);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(0, 0, 0, 0.7);--bs-navbar-brand-hover-color:rgba(0, 0, 0, 0.7);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2868, 68, 68, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.9);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:rgba(255, 255, 255, 0.9);--bs-navbar-brand-color:rgba(255, 255, 255, 0.9);--bs-navbar-brand-hover-color:rgba(255, 255, 255, 0.9);--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:0.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0}.card>.list-group:last-child{border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23444'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2324143c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-focus-border-color:#593196;--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(89, 49, 150, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:var(--bs-accordion-btn-focus-border-color);outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:not(:first-of-type){border-top:0}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%239b83c0'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%239b83c0'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 0.25rem rgba(89, 49, 150, 0.25);--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#593196;--bs-pagination-active-border-color:#593196;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(var(--bs-border-width) * -1)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:#ededed;--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#593196;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:#17141f;--bs-list-group-border-color:transparent;--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:0.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:#2e283e;--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:#5c507c;--bs-list-group-disabled-bg:#17141f;--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#17141f;--bs-list-group-active-border-color:#17141f;display:flex;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity:0.5;--bs-btn-close-hover-opacity:0.75;--bs-btn-close-focus-shadow:0 0 0 0.25rem rgba(89, 49, 150, 0.25);--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;--bs-btn-close-white-filter:invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color)}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:0.5rem;--bs-modal-color: ;--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin:calc(-.5 * var(--bs-modal-header-padding-y)) calc(-.5 * var(--bs-modal-header-padding-x)) calc(-.5 * var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:0.5rem;--bs-tooltip-padding-y:0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.9;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-border,.spinner-grow{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media (min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media (min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media (min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media (min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media (min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(89,49,150,var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(169,145,212,var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(19,185,85,var(--bs-bg-opacity,1))!important}.text-bg-info{color:#fff!important;background-color:RGBA(0,156,220,var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#fff!important;background-color:RGBA(239,163,29,var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(252,57,57,var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(249,248,252,var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(23,20,31,var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important}.link-primary:focus,.link-primary:hover{color:RGBA(71,39,120,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(71,39,120,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(71,39,120,var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important}.link-secondary:focus,.link-secondary:hover{color:RGBA(135,116,170,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(135,116,170,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(135,116,170,var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important}.link-success:focus,.link-success:hover{color:RGBA(15,148,68,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(15,148,68,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(15,148,68,var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important}.link-info:focus,.link-info:hover{color:RGBA(0,125,176,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(0,125,176,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(0,125,176,var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important}.link-warning:focus,.link-warning:hover{color:RGBA(191,130,23,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(191,130,23,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(191,130,23,var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important}.link-danger:focus,.link-danger:hover{color:RGBA(202,46,46,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(202,46,46,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(202,46,46,var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important}.link-light:focus,.link-light:hover{color:RGBA(250,249,253,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(250,249,253,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(250,249,253,var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important}.link-dark:focus,.link-dark:hover{color:RGBA(18,16,25,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(18,16,25,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(18,16,25,var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:focus,.link-body-emphasis:hover{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-underline-offset:0.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-none{-o-object-fit:none!important;object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10{--bs-link-opacity:0.1}.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25{--bs-link-opacity:0.25}.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50{--bs-link-opacity:0.5}.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75{--bs-link-opacity:0.75}.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100{--bs-link-opacity:1}.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1{text-underline-offset:0.125em!important}.link-offset-1-hover:hover{text-underline-offset:0.125em!important}.link-offset-2{text-underline-offset:0.25em!important}.link-offset-2-hover:hover{text-underline-offset:0.25em!important}.link-offset-3{text-underline-offset:0.375em!important}.link-offset-3-hover:hover{text-underline-offset:0.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0{--bs-link-underline-opacity:0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10{--bs-link-underline-opacity:0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25{--bs-link-underline-opacity:0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50{--bs-link-underline-opacity:0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75{--bs-link-underline-opacity:0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100{--bs-link-underline-opacity:1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-sm-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-sm-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-sm-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-sm-none{-o-object-fit:none!important;object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-sm-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-sm-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-sm-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-md-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-md-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-md-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-md-none{-o-object-fit:none!important;object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-md-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-md-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-md-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-lg-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-lg-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-lg-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-lg-none{-o-object-fit:none!important;object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-lg-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-lg-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-lg-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xl-none{-o-object-fit:none!important;object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xxl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xxl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xxl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xxl-none{-o-object-fit:none!important;object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xxl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xxl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xxl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}:root{color-scheme:light}.btn.active:focus,.btn:active,.btn:active:focus,.btn:focus{outline:0}.btn-secondary{color:#17141f;background-color:#fff;border-color:#ccc}.btn-secondary:hover{color:#17141f;background-color:#ededed;border-color:#adb5bd}.btn-secondary.disabled{color:#23202a;background-color:#fff;border-color:#cfcfcf}.btn-warning{color:#fff}.btn-primary:focus{box-shadow:0 0 5px #6a46a1}.btn-secondary:focus{box-shadow:0 0 5px #cbc8d0}.btn-success:focus{box-shadow:0 0 5px #2bc066}.btn-info:focus{box-shadow:0 0 5px #1aa6e0}.btn-warning:focus{box-shadow:0 0 5px #f1ac34}.btn-danger:focus{box-shadow:0 0 5px #fc4d4d}.btn.disabled:focus{box-shadow:none}.table .thead-dark th{background-color:#a991d4;border-color:rgba(0,0,0,.05)}.form-control:focus{box-shadow:0 0 5px rgba(100,65,164,.4)}.nav-tabs .nav-link,.nav-tabs .nav-link.active{border-width:0 0 1px}.nav-tabs .nav-link.active,.nav-tabs .nav-link.active:focus,.nav-tabs .nav-link.active:hover,.nav-tabs .nav-link:hover{border-bottom:1px solid #593196}.nav-tabs .nav-item+.nav-item{margin-left:0}.breadcrumb-item.active{color:#444}.badge.bg-light{color:#17141f}.progress{height:8px}.list-group-item{color:rgba(255,255,255,.8)}.list-group-item.active,.list-group-item:focus,.list-group-item:hover{color:#fff}.list-group-item.active{font-weight:700}.list-group-item.active:hover{background-color:#2e283e}.list-group-item.disabled:hover{color:#5c507c} \ No newline at end of file diff --git a/Management.Web/wwwroot/css/open-iconic/FONT-LICENSE b/Management.Web/wwwroot/css/open-iconic/FONT-LICENSE deleted file mode 100644 index 8ae8650..0000000 --- a/Management.Web/wwwroot/css/open-iconic/FONT-LICENSE +++ /dev/null @@ -1,86 +0,0 @@ -SIL OPEN FONT LICENSE Version 1.1 - -Copyright (c) 2014 Waybury - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Management.Web/wwwroot/css/open-iconic/ICON-LICENSE b/Management.Web/wwwroot/css/open-iconic/ICON-LICENSE deleted file mode 100644 index af73356..0000000 --- a/Management.Web/wwwroot/css/open-iconic/ICON-LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Waybury - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/Management.Web/wwwroot/css/open-iconic/README.md b/Management.Web/wwwroot/css/open-iconic/README.md deleted file mode 100644 index 64f55be..0000000 --- a/Management.Web/wwwroot/css/open-iconic/README.md +++ /dev/null @@ -1,114 +0,0 @@ -[Open Iconic v1.1.1](https://github.com/iconic/open-iconic) -=========== - -### Open Iconic is the open source sibling of [Iconic](https://github.com/iconic/open-iconic). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](https://github.com/iconic/open-iconic) - - - -## What's in Open Iconic? - -* 223 icons designed to be legible down to 8 pixels -* Super-light SVG files - 61.8 for the entire set -* SVG sprite—the modern replacement for icon fonts -* Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats -* Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats -* PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. - - -## Getting Started - -#### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](https://github.com/iconic/open-iconic) and [Reference](https://github.com/iconic/open-iconic) sections. - -### General Usage - -#### Using Open Iconic's SVGs - -We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). - -``` -icon name -``` - -#### Using Open Iconic's SVG Sprite - -Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. - -Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* - -``` - - - -``` - -Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. - -``` -.icon { - width: 16px; - height: 16px; -} -``` - -Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. - -``` -.icon-account-login { - fill: #f00; -} -``` - -To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). - -#### Using Open Iconic's Icon Font... - - -##### …with Bootstrap - -You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` - - -``` - -``` - - -``` - -``` - -##### …with Foundation - -You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` - -``` - -``` - - -``` - -``` - -##### …on its own - -You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` - -``` - -``` - -``` - -``` - - -## License - -### Icons - -All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). - -### Fonts - -All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). diff --git a/Management.Web/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/Management.Web/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css deleted file mode 100644 index 4664f2e..0000000 --- a/Management.Web/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} \ No newline at end of file diff --git a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.eot deleted file mode 100644 index f98177d..0000000 Binary files a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.eot and /dev/null differ diff --git a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.otf deleted file mode 100644 index f6bd684..0000000 Binary files a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.otf and /dev/null differ diff --git a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.svg deleted file mode 100644 index cf42942..0000000 --- a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.svg +++ /dev/null @@ -1,543 +0,0 @@ - - - - - -Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014 - By P.J. Onori -Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf deleted file mode 100644 index fab6048..0000000 Binary files a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf and /dev/null differ diff --git a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.woff deleted file mode 100644 index f930998..0000000 Binary files a/Management.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.woff and /dev/null differ diff --git a/Management.Web/wwwroot/css/site.css b/Management.Web/wwwroot/css/site.css deleted file mode 100644 index 28f202f..0000000 --- a/Management.Web/wwwroot/css/site.css +++ /dev/null @@ -1,106 +0,0 @@ -@import url("open-iconic/font/css/open-iconic-bootstrap.min.css"); -html, -body { - font-family: "DM Sans", sans-serif; -} -:root, #page, main { - padding: 0; - margin: 0; -} - -#blazor-error-ui { - background: lightyellow; - bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); - display: none; - left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; - position: fixed; - width: 100%; - z-index: 1000; -} - -#blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; -} - -.blazor-error-boundary { - background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) - no-repeat 1rem/1.8rem, - #b32121; - padding: 1rem 1rem 1rem 3.7rem; - color: white; -} - -.blazor-error-boundary::after { - content: "An error has occurred."; -} - -.monaco-editor-container { - height: 100%; -} -/* .minimap { - display: none; -} */ - -/* .decorationGlyphMarginClass { - background: red; -} - -.decorationContentClass { - background: lightblue; -} */ - - - -/* scroll bar */ - -/* width */ -::-webkit-scrollbar { - width: 8px; -} - -/* Track */ -::-webkit-scrollbar-track { - /* box-shadow: inset 0 0 5px grey; */ - border-radius: 8px; -} - -/* Handle */ -::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.6); - border-radius: 8px; - border-style: solid; - border-color: rgba(255, 255, 255, 0.3); - border-width: 1px; - /* outline-offset: 2px; */ -} - -/* Handle on hover */ -::-webkit-scrollbar-thumb:hover { - background: rgb(31, 31, 31); -} - - - -/* monaco editor */ - -.monaco-editor-background, .monaco-editor .margin { - background-color: var(--bs-dark-bg-subtle) !important; -} - -/* markdown styling */ - -blockquote { - padding: 10px 20px; - margin: 20px 0; - border-left: 5px solid #909090; -} - -strong { - color: rgb(145 149 221); - font-size: large; -} \ No newline at end of file diff --git a/Management.Web/wwwroot/favicon.png b/Management.Web/wwwroot/favicon.png deleted file mode 100644 index 8422b59..0000000 Binary files a/Management.Web/wwwroot/favicon.png and /dev/null differ diff --git a/Management/DiagnosticsConfig.cs b/Management/DiagnosticsConfig.cs deleted file mode 100644 index 2417dcc..0000000 --- a/Management/DiagnosticsConfig.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Diagnostics; -using System.Security.Policy; - -public static class DiagnosticsConfig -{ - public const string SourceName = "canvas-management-source"; - public readonly static ActivitySource Source = new(SourceName); -} diff --git a/Management/Features/Calendar/CalendarMonth.cs b/Management/Features/Calendar/CalendarMonth.cs deleted file mode 100644 index 32d2742..0000000 --- a/Management/Features/Calendar/CalendarMonth.cs +++ /dev/null @@ -1,82 +0,0 @@ -public class CalendarMonth -{ - public int Year { get; } - public int Month { get; } - public IEnumerable> Weeks - { - get => - DaysByWeek.Select( - week => week.Select(d => d?.Day) - ); - - } - - public IEnumerable> DaysByWeek - { - get - { - var weeks = new List>(); - var weeksInMonth = WeeksInMonth(Year, Month); - var daysInMonth = DateTime.DaysInMonth(Year, Month); - - var firstDayOfMonth = new DateTime(Year, Month, 1).DayOfWeek; - - var currentDay = 1; - for (int i = 0; i < weeksInMonth; i++) - { - var thisWeek = new List(); - if (i == 0 && firstDayOfMonth != DayOfWeek.Sunday) - { - for (int j = 0; j < 7; j++) - { - if (j < (int)firstDayOfMonth) - { - thisWeek.Add(null); - } - else - { - thisWeek.Add(new DateTime(Year, Month, currentDay)); - currentDay++; - } - } - } - else - { - for (int j = 0; j < 7; j++) - { - if (currentDay <= daysInMonth) - { - thisWeek.Add(new DateTime(Year, Month, currentDay)); - currentDay++; - } - else - { - thisWeek.Add(null); - } - } - } - weeks.Add(thisWeek); - } - return weeks; - } - } - - public static int WeeksInMonth(int year, int month) - { - var firstDayOfMonth = new DateTime(year, month, 1).DayOfWeek; - var daysInMonth = DateTime.DaysInMonth(year, month); - var longDaysInMonth = daysInMonth + (int)(firstDayOfMonth); - var weeks = longDaysInMonth / 7; - if ((longDaysInMonth % 7) > 0) - { - weeks += 1; - } - return weeks; - } - - public CalendarMonth(int year, int month) - { - Year = year; - Month = month; - } -} diff --git a/Management/Features/Calendar/SemesterPlanner.cs b/Management/Features/Calendar/SemesterPlanner.cs deleted file mode 100644 index b994d2b..0000000 --- a/Management/Features/Calendar/SemesterPlanner.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CanvasModel.EnrollmentTerms; - -public class SemesterPlanner -{ - public static IEnumerable GetMonthsBetweenDates( - DateTime startDate, - DateTime endDate - ) - { - var monthsInTerm = 1 + ((endDate.Year - startDate.Year) * 12) + endDate.Month - startDate.Month; - - return Enumerable - .Range(0, monthsInTerm) - .Select(monthDiff => - { - var month = ((startDate.Month + monthDiff - 1) % 12) + 1; - var year = startDate.Year + ((startDate.Month + monthDiff - 1) / 12); - - return new CalendarMonth(year, month); - }); - } -} diff --git a/Management/Features/Configuration/AssignmentEditorContext.cs b/Management/Features/Configuration/AssignmentEditorContext.cs deleted file mode 100644 index 112ad53..0000000 --- a/Management/Features/Configuration/AssignmentEditorContext.cs +++ /dev/null @@ -1,186 +0,0 @@ -using CanvasModel.Modules; -using LocalModels; -using Management.Planner; -using Management.Services; -using Management.Services.Canvas; - -public class AssignmentEditorContext -{ - public event Action? StateHasChanged; - - public ICanvasService canvas { get; } - private CoursePlanner planner { get; } - - public AssignmentEditorContext( - MyLogger logger, - ICanvasService canvas, - CoursePlanner planner - ) - { - this.logger = logger; - this.canvas = canvas; - this.planner = planner; - } - - private LocalAssignment? _assignment; - private readonly MyLogger logger; - - public LocalAssignment? Assignment - { - get => _assignment; - set - { - _assignment = value; - StateHasChanged?.Invoke(); - } - } - - public void SaveAssignment(LocalAssignment newAssignment) - { - if (planner.LocalCourse != null && Assignment != null) - { - // run discovery on Assignment, it was the last stored version of the assignment - var currentModule = getCurrentLocalModule(Assignment, planner.LocalCourse); - - var updatedModules = planner.LocalCourse.Modules - .Select( - m => - m.Name == currentModule.Name - ? currentModule with - { - Assignments = currentModule.Assignments - .Select(a => a.Name == Assignment.Name ? newAssignment : a) - .ToArray() - } - : m - ) - .ToArray(); - planner.LocalCourse = planner.LocalCourse with { Modules = updatedModules }; - - Assignment = newAssignment; - } - } - - public async Task UpdateInCanvas(ulong canvasAssignmentId) - { - logger.Log("started to update assignment in canvas"); - if (Assignment == null) - { - logger.Log("cannot update null assignment in canvas"); - return; - } - - - await planner.LoadCanvasData(); - if (planner.CanvasData == null) - { - logger.Log("cannot update assignment in canvas, failed to retrieve current assignments"); - return; - } - if (planner.LocalCourse == null) - { - logger.Log("cannot update assignment in canvas, no course stored in planner"); - return; - } - if (planner.LocalCourse.Settings.CanvasId == null) - { - logger.Log("Cannot update assignment with null local course canvas id"); - return; - } - var assignmentInCanvas = planner.CanvasData.Assignments?.FirstOrDefault(a => a.Id == canvasAssignmentId); - if (assignmentInCanvas == null) - { - logger.Log("cannot update assignment in canvas, could not find canvas assignment with id: " + canvasAssignmentId); - return; - } - // Console.WriteLine(JsonSerializer.Serialize(Assignment.LocalAssignmentGroupName)); - // Console.WriteLine(JsonSerializer.Serialize(planner.LocalCourse.Settings.AssignmentGroups)); - - var canvasAssignmentGroupId = Assignment.GetCanvasAssignmentGroupId(planner.LocalCourse.Settings.AssignmentGroups); - - if (canvasAssignmentGroupId == null) - { - logger.Log("cannot update assignment in canvas, could not get assignment group id: " + assignmentInCanvas.AssignmentGroupId); - return; - } - - await canvas.Assignments.Update( - courseId: (ulong)planner.LocalCourse.Settings.CanvasId, - canvasAssignmentId: canvasAssignmentId, - localAssignment: Assignment, - canvasAssignmentGroupId: (ulong)canvasAssignmentGroupId - ); - } - - public async Task AddAssignmentToCanvas() - { - logger.Log("started to add assignment to canvas"); - if (Assignment == null) - { - logger.Log("cannot add null assignment to canvas"); - return; - } - await planner.LoadCanvasData(); - if (planner.CanvasData == null) - { - logger.Log("cannot add assignment to canvas, failed to retrieve current assignments"); - return; - } - if (planner.LocalCourse == null) - { - logger.Log("cannot add assignment to canvas, no course stored in planner"); - return; - } - - - - var courseCanvasId = planner.LocalCourse.Settings.CanvasId; - if (courseCanvasId == null) - { - logger.Log("cannot add assignment to canvas if there is no course canvas id"); - return; - } - - var createdAssignmentCanvasId = await planner.LocalCourse.SyncAssignmentToCanvas( - canvasCourseId: (ulong)courseCanvasId, - localAssignment: Assignment, - canvasAssignments: planner.CanvasData.Assignments, - canvas: canvas - ); - - var canvasModule = getCurrentCanvasModule(Assignment, planner.LocalCourse); - - await canvas.CreateModuleItem( - (ulong)courseCanvasId, - canvasModule.Id, - Assignment.Name, - - "Assignment", - createdAssignmentCanvasId - ); - - var module = getCurrentLocalModule(Assignment, planner.LocalCourse); - await module.SortModuleItems( - (ulong)courseCanvasId, - canvasModule.Id, - canvas - ); - logger.Log($"finished adding assignment {Assignment.Name} to canvas"); - } - - private static LocalModule getCurrentLocalModule(LocalAssignment assignment, LocalCourse course) - { - return course.Modules.FirstOrDefault( - m => m.Assignments.Select(a => a.Name).Contains(assignment.Name) - ) - ?? throw new Exception("could not find current module in assignment editor context"); - } - - private CanvasModule getCurrentCanvasModule(LocalAssignment assignment, LocalCourse course) - { - var localModule = getCurrentLocalModule(assignment, course); - var canvasModule = planner.CanvasData?.Modules.FirstOrDefault(m => m.Name == localModule.Name) - ?? throw new Exception($"error in assignment context, canvas module with name {localModule.Name} not found in planner"); - return canvasModule; - } -} diff --git a/Management/Features/Configuration/CoursePlanner.cs b/Management/Features/Configuration/CoursePlanner.cs deleted file mode 100644 index 6018a83..0000000 --- a/Management/Features/Configuration/CoursePlanner.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System.Text.RegularExpressions; - -using CanvasModel; -using CanvasModel.Assignments; -using CanvasModel.Courses; -using CanvasModel.EnrollmentTerms; -using CanvasModel.Modules; -using CanvasModel.Pages; -using CanvasModel.Quizzes; - -using LocalModels; - -using Management.Services; -using Management.Services.Canvas; - -namespace Management.Planner; - - -public record CanvasCourseData -{ - public required IEnumerable Assignments { get; init; } - public required IEnumerable AssignmentGroups { get; init; } - public required IEnumerable Quizzes { get; init; } - public required IEnumerable Modules { get; init; } - public required IEnumerable Pages { get; init; } - public required Dictionary> ModulesItems { get; init; } -} - -public class CoursePlanner -{ - private readonly MyLogger logger; - private readonly IFileStorageManager fileStorageManager; - private readonly ICanvasService canvas; - private readonly ILogger _otherLogger; - - public bool LoadingCanvasData { get; internal set; } = false; - - public CoursePlanner( - MyLogger logger, - IFileStorageManager fileStorageManager, - ICanvasService canvas, - ILogger otherLogger - ) - { - this.logger = logger; - this.fileStorageManager = fileStorageManager; - this.canvas = canvas; - _otherLogger = otherLogger; - _otherLogger.LogInformation("testing other logging"); - } - - private Timer? _debounceTimer; - private int _debounceInterval = 1000; - private LocalCourse? _localCourse { get; set; } - private LocalCourse? _lastSavedCourse { get; set; } - private string loadedCourseName = ""; - public LocalCourse? LocalCourse - { - get => _localCourse; - set - { - if (value == null) - { - _localCourse = null; - StateHasChanged?.Invoke(); - - loadedCourseName = ""; - return; - } - - var verifiedCourse = value.GeneralCourseCleanup(); - loadedCourseName = verifiedCourse.Settings.Name; - - if (_localCourse == null) - { - _localCourse = verifiedCourse; - _lastSavedCourse = verifiedCourse; - StateHasChanged?.Invoke(); - return; - } - - saveCourseToFile(verifiedCourse); - - _localCourse = verifiedCourse; - StateHasChanged?.Invoke(); - } - } - - private void saveCourseToFile(LocalCourse courseAsOfDebounce) - { - _debounceTimer?.Dispose(); - _debounceTimer = new Timer( - async (_) => - { - _debounceTimer?.Dispose(); - - // ignore initial load of course - if (LocalCourse == null) - { - logger.Trace("saving course as of debounce call time"); - await fileStorageManager.SaveCourseAsync(courseAsOfDebounce, null); - _lastSavedCourse = courseAsOfDebounce; - } - else - { - if (_lastSavedCourse == null) - { - logger.Trace("not saving course, no prevous saved course"); - _lastSavedCourse = LocalCourse ?? courseAsOfDebounce; - return; - } - - - logger.Trace("Saving latest version of file"); - var courseToSave = LocalCourse; - await fileStorageManager.SaveCourseAsync(courseToSave, _lastSavedCourse); - _lastSavedCourse = courseToSave; - - } - }, - null, - _debounceInterval, - Timeout.Infinite - ); - } - - public event Action? StateHasChanged; - - public CanvasCourseData? CanvasData { get; internal set; } - - public async Task LoadCanvasData() - { - using var activity = DiagnosticsConfig.Source.StartActivity("Loading Canvas Data to Course Planner"); - LoadingCanvasData = true; - StateHasChanged?.Invoke(); - - var canvasId = - LocalCourse?.Settings.CanvasId ?? throw new Exception("no canvas id found for selected course"); - - var assignmentsTask = canvas.Assignments.GetAll(canvasId); - var quizzesTask = canvas.Quizzes.GetAll(canvasId); - var modulesTask = canvas.Modules.GetModules(canvasId); - var assignmentGroupsTask = canvas.AssignmentGroups.GetAll(canvasId); - var coursePagesTask = canvas.Pages.GetAll(canvasId); - - - var canvasAssignments = (await assignmentsTask) ?? throw new Exception("Error loading canvas assignments"); - var canvasQuizzes = (await quizzesTask) ?? throw new Exception("Error loading canvas quizzes"); - var canvasAssignmentGroups = (await assignmentGroupsTask) ?? throw new Exception("Error loading canvas assignment groups"); - var canvasPages = (await coursePagesTask) ?? throw new Exception("Error loading canvas pages"); - var canvasModules = (await modulesTask) ?? throw new Exception("Error loading canvas modules"); - var canvasModulesItems = (await canvas.Modules.GetAllModulesItems(canvasId, canvasModules)) ?? throw new Exception("Error loading canvas module items"); - - CanvasData = new CanvasCourseData - { - Assignments = canvasAssignments, - Quizzes = canvasQuizzes, - AssignmentGroups = canvasAssignmentGroups, - Pages = canvasPages, - Modules = canvasModules, - ModulesItems = canvasModulesItems, - }; - - LoadingCanvasData = false; - StateHasChanged?.Invoke(); - } - - public async Task CreateModule(LocalModule newModule) - { - if (LocalCourse == null) - return; - var canvasCourseId = - LocalCourse.Settings.CanvasId ?? throw new Exception("no course canvas id to use to create module"); - await canvas.Modules.CreateModule(canvasCourseId, newModule.Name); - var canvasModules = await canvas.Modules.GetModules(canvasCourseId); - if (CanvasData != null) - { - CanvasData = CanvasData with - { - Modules = canvasModules - }; - } - } - - public void Clear() - { - CanvasData = null; - LocalCourse = null; - } - - public async Task SyncAssignmentGroups() - { - if (LocalCourse == null) - return; - - var canvasCourseId = - LocalCourse.Settings.CanvasId ?? throw new Exception("no course canvas id to use to create module"); - - var canvasAssignmentGroups = await canvas.AssignmentGroups.GetAll(canvasCourseId); - - await LocalCourse.EnsureAllAssignmentGroupsExistInCanvas(canvasCourseId, canvasAssignmentGroups, canvas); - - canvasAssignmentGroups = await canvas.AssignmentGroups.GetAll(canvasCourseId); - - LocalCourse = LocalCourse with - { - Settings = LocalCourse.Settings with - { - AssignmentGroups = LocalCourse.Settings.AssignmentGroups.Select(g => - { - var canvasGroup = canvasAssignmentGroups.FirstOrDefault(c => c.Name == g.Name); - return canvasGroup == null - ? g - : g with { CanvasId = canvasGroup.Id }; - }) - } - }; - CanvasData = CanvasData with - { - AssignmentGroups = canvasAssignmentGroups - }; - } -} diff --git a/Management/Features/Configuration/CoursePlannerValidationExtensions.cs b/Management/Features/Configuration/CoursePlannerValidationExtensions.cs deleted file mode 100644 index 3697c48..0000000 --- a/Management/Features/Configuration/CoursePlannerValidationExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -using CanvasModel.Assignments; -using CanvasModel.Modules; -using CanvasModel.Quizzes; -using LocalModels; - -namespace Management.Planner; - -public static class CoursePlannerExtensions -{ - public static LocalCourse GeneralCourseCleanup(this LocalCourse incomingCourse) - { - var cleanModules = incomingCourse.Modules - .Select( - module => - module with - { - Assignments = module.Assignments - .OrderBy(a => a.DueAt) - .Select(a => a.validateSubmissionTypes()) - .Select(a => a.validateDates()) - .ToArray() - } - ) - .ToArray(); - - var cleanStartDay = new DateTime( - incomingCourse.Settings.StartDate.Year, - incomingCourse.Settings.StartDate.Month, - incomingCourse.Settings.StartDate.Day - ); - var cleanEndDay = new DateTime( - incomingCourse.Settings.EndDate.Year, - incomingCourse.Settings.EndDate.Month, - incomingCourse.Settings.EndDate.Day - ); - - return incomingCourse with - { - Modules = cleanModules, - Settings = incomingCourse.Settings with - { - StartDate = cleanStartDay, - EndDate = cleanEndDay, - } - }; - } - - public static LocalAssignment validateSubmissionTypes(this LocalAssignment assignment) - { - var containsDiscussion = - assignment.SubmissionTypes.FirstOrDefault(t => t == AssignmentSubmissionType.DISCUSSION_TOPIC) != null; - - if (containsDiscussion) - return assignment with { SubmissionTypes = new string[] { AssignmentSubmissionType.DISCUSSION_TOPIC } }; - return assignment; - } - - public static LocalAssignment validateDates(this LocalAssignment assignment) - { - var dueAt = assignment.DueAt.AddMilliseconds(0).AddMilliseconds(0); - var lockAt = assignment.LockAt?.AddMilliseconds(0).AddMilliseconds(0); - return assignment with - { - DueAt = dueAt, - LockAt = lockAt - }; - } -} diff --git a/Management/Features/Configuration/PageEditorContext.cs b/Management/Features/Configuration/PageEditorContext.cs deleted file mode 100644 index 760abe2..0000000 --- a/Management/Features/Configuration/PageEditorContext.cs +++ /dev/null @@ -1,191 +0,0 @@ -using CanvasModel.Modules; -using LocalModels; -using Management.Planner; -using Management.Services; -using Management.Services.Canvas; - -public class PageEditorContext( - CoursePlanner planner, - ICanvasService canvas, - MyLogger logger) -{ - public event Action? StateHasChanged; - private CoursePlanner planner { get; } = planner; - private ICanvasService canvas { get; } = canvas; - private readonly MyLogger logger = logger; - - - private LocalCoursePage? _page; - - private LocalModule? _module; - - - public LocalCoursePage? Page - { - get => _page; - set - { - if (_page == null && value != null && planner != null && planner.LocalCourse != null) - { - _module = getCurrentLocalModule(value, planner.LocalCourse); - } - _page = value; - StateHasChanged?.Invoke(); - } - } - - public void SavePage(LocalCoursePage newPage) - { - if (planner.LocalCourse != null && _module != null && Page != null) - { - // use Page not newPage because it is the version that was last stored - - var updatedModules = planner.LocalCourse.Modules - .Select( - m => - m.Name == _module.Name - ? m with - { - Pages = m.Pages - .Select(p => p == Page ? newPage : p) - .ToArray() - } - : m - ) - .ToArray(); - - planner.LocalCourse = planner.LocalCourse with { Modules = updatedModules }; - Page = newPage; - } - } - - public void DeletePage() - { - if (planner.LocalCourse != null && Page != null && _module != null) - { - // not dealing with canvas rn - var updatedModules = planner.LocalCourse.Modules - .Select(m => m.Name != _module.Name - ? m - : m with - { - Pages = m.Pages.Where(p => p == Page).ToArray() - } - ) - .ToArray(); - - planner.LocalCourse = planner.LocalCourse with { Modules = updatedModules }; - Page = null; - } - } - - - public async Task AddPageToCanvas() - { - logger.Log("started to add page to canvas"); - if (Page == null) - { - logger.Log("cannot add null page to canvas"); - return; - } - await planner.LoadCanvasData(); - if (planner.CanvasData == null) - { - logger.Log("cannot add page to canvas, failed to retrieve current pages"); - return; - } - if (planner.LocalCourse == null) - { - logger.Log("cannot add page to canvas, no course stored in planner"); - return; - } - var canvasPage = await planner.LocalCourse.AddPageToCanvas(Page, canvas); - - - - var courseCanvasId = planner.LocalCourse.Settings.CanvasId; - if (courseCanvasId == null) - { - logger.Log("was able to add page to canvas, but errored while making module item. CourseCanvasId is null"); - return; - } - - var canvasModule = getCurrentCanvasModule(Page, planner.LocalCourse); - - if (canvasPage != null) - { - await canvas.CreatePageModuleItem( - (ulong)courseCanvasId, - canvasModule.Id, - Page.Name, - canvasPage - ); - - var currentModule = getCurrentLocalModule(Page, planner.LocalCourse); - await currentModule.SortModuleItems( - (ulong)courseCanvasId, - canvasModule.Id, - canvas - ); - } - logger.Log($"finished adding page {Page.Name} to canvas"); - } - - public async Task UpdateInCanvas(ulong canvasPageId) - { - - logger.Log("started to update page in canvas"); - if (Page == null) - { - logger.Log("cannot update null page in canvas"); - return; - } - - - await planner.LoadCanvasData(); - if (planner.CanvasData == null) - { - logger.Log("cannot update page in canvas, failed to retrieve current pages"); - return; - } - if (planner.LocalCourse == null) - { - logger.Log("cannot update page in canvas, no course stored in planner"); - return; - } - if (planner.LocalCourse.Settings.CanvasId == null) - { - logger.Log("Cannot update page with null local course canvas id"); - return; - } - var assignmentInCanvas = planner.CanvasData?.Pages.FirstOrDefault(p => p.PageId == canvasPageId); - if (assignmentInCanvas == null) - { - logger.Log("cannot update page in canvas, could not find canvas page with id: " + canvasPageId); - return; - } - - - await canvas.Pages.Update( - courseId: (ulong)planner.LocalCourse.Settings.CanvasId, - canvasPageId: canvasPageId, - localCoursePage: Page - ); - } - - private static LocalModule getCurrentLocalModule(LocalCoursePage page, LocalCourse course) - { - return course.Modules.FirstOrDefault( - m => m.Pages.Contains(page) - ) - ?? throw new Exception("could not find current module in page editor context"); - } - - private CanvasModule getCurrentCanvasModule(LocalCoursePage quiz, LocalCourse course) - { - var localModule = getCurrentLocalModule(quiz, course); - var canvasModule = planner.CanvasData?.Modules.FirstOrDefault(m => m.Name == localModule.Name) - ?? throw new Exception($"error in page context, canvas module with name {localModule.Name} not found in planner"); - return canvasModule; - } -} diff --git a/Management/Features/Configuration/QuizEditorContext.cs b/Management/Features/Configuration/QuizEditorContext.cs deleted file mode 100644 index 4810b30..0000000 --- a/Management/Features/Configuration/QuizEditorContext.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Reflection.Metadata.Ecma335; - -using CanvasModel.Modules; - -using LocalModels; - -using Management.Planner; -using Management.Services; -using Management.Services.Canvas; - -public class QuizEditorContext( - CoursePlanner planner, - ICanvasService canvas, - MyLogger logger) -{ - public event Action? StateHasChanged; - private CoursePlanner planner { get; } = planner; - private ICanvasService canvas { get; } = canvas; - private readonly MyLogger logger = logger; - - - private LocalQuiz? _quiz; - - private LocalModule? _module; - - public LocalQuiz? Quiz - { - get => _quiz; - set - { - if (_quiz == null && value != null && planner != null && planner.LocalCourse != null) - { - _module = getCurrentLocalModule(value, planner.LocalCourse); - } - _quiz = value; - StateHasChanged?.Invoke(); - } - } - - public void SaveQuiz(LocalQuiz newQuiz) - { - using var activity = DiagnosticsConfig.Source.CreateActivity("quiz creation requested", System.Diagnostics.ActivityKind.Server); - - if (planner.LocalCourse != null && _module != null && Quiz != null) - { - // use Quiz not newQuiz because it is the version that was last stored - - var updatedModules = planner.LocalCourse.Modules - .Select( - m => - m.Name == _module.Name - ? m with - { - Quizzes = m.Quizzes - .Select(q => q.Name + q.Description == Quiz.Name + Quiz.Description ? newQuiz : q) - .ToArray() - } - : m - ) - .ToArray(); - - planner.LocalCourse = planner.LocalCourse with { Modules = updatedModules }; - Quiz = newQuiz; - } - } - - public void DeleteQuiz() - { - if (planner.LocalCourse != null && Quiz != null && _module != null) - { - - var updatedModules = planner.LocalCourse.Modules - .Select(m => m.Name != _module.Name - ? m - : m with - { - Quizzes = m.Quizzes.Where(q => q.Name + q.Description != Quiz.Name + Quiz.Description).ToArray() - } - ) - .ToArray(); - - planner.LocalCourse = planner.LocalCourse with { Modules = updatedModules }; - Quiz = null; - } - } - - - public async Task AddQuizToCanvas() - { - logger.Log("started to add quiz to canvas"); - if (Quiz == null) - { - logger.Log("cannot add null quiz to canvas"); - return; - } - await planner.LoadCanvasData(); - if (planner.CanvasData == null) - { - logger.Log("cannot add quiz to canvas, failed to retrieve current quizzes"); - return; - } - if (planner.LocalCourse == null) - { - logger.Log("cannot add quiz to canvas, no course stored in planner"); - return; - } - var canvasQuizId = await planner.LocalCourse.AddQuizToCanvas(Quiz, canvas); - - - - var courseCanvasId = planner.LocalCourse.Settings.CanvasId; - if (courseCanvasId == null) - { - logger.Log("was able to add quiz to canvas, but errored while making module item. CourseCanvasId is null"); - return; - } - - var canvasModule = getCurrentCanvasModule(Quiz, planner.LocalCourse); - - await canvas.CreateModuleItem( - (ulong)courseCanvasId, - canvasModule.Id, - Quiz.Name, - "Quiz", - (ulong)canvasQuizId - ); - - var module = getCurrentLocalModule(Quiz, planner.LocalCourse); - await module.SortModuleItems( - (ulong)courseCanvasId, - canvasModule.Id, - canvas - ); - logger.Log($"finished adding quiz {Quiz.Name} to canvas"); - } - - private static LocalModule getCurrentLocalModule(LocalQuiz quiz, LocalCourse course) - { - return course.Modules.FirstOrDefault( - m => m.Quizzes.Select(q => q.Name + q.Description).Contains(quiz.Name + quiz.Description) - ) - ?? throw new Exception("could not find current module in quiz editor context"); - } - - private CanvasModule getCurrentCanvasModule(LocalQuiz quiz, LocalCourse course) - { - var localModule = getCurrentLocalModule(quiz, course); - var canvasModule = planner.CanvasData?.Modules.FirstOrDefault(m => m.Name == localModule.Name) - ?? throw new Exception($"error in quiz context, canvas module with name {localModule.Name} not found in planner"); - return canvasModule; - } -} diff --git a/Management/Features/Configuration/Synchronization/AssignemntGroupSyncronizationExtensions.cs b/Management/Features/Configuration/Synchronization/AssignemntGroupSyncronizationExtensions.cs deleted file mode 100644 index 89568f2..0000000 --- a/Management/Features/Configuration/Synchronization/AssignemntGroupSyncronizationExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using CanvasModel.Assignments; -using CanvasModel.Modules; -using LocalModels; -using Management.Services.Canvas; - -namespace Management.Planner; - -public static partial class AssignmentGroupSyncronizationExtensions -{ - internal static async Task> EnsureAllAssignmentGroupsExistInCanvas( - this LocalCourse localCourse, - ulong courseCanvasId, - IEnumerable canvasAssignmentGroups, - ICanvasService canvas - ) - { - var canvasAssignmentGroupIds = canvasAssignmentGroups.Select(g => g.Id).ToArray(); - var assignmentGroups = await Task.WhenAll((Task[])localCourse.Settings.AssignmentGroups.Select( - async assignmentGroup => - { - var canvasGroupWithSameName = canvasAssignmentGroups.FirstOrDefault( - cg => cg.Name.Equals(assignmentGroup.Name) - ); - if (canvasGroupWithSameName == null) - return await canvas.AssignmentGroups.Create(courseCanvasId, assignmentGroup); - - var correctGroup = assignmentGroup with { CanvasId = canvasGroupWithSameName.Id }; - - var needsUpdate = canvasGroupWithSameName.GroupWeight != correctGroup.Weight; - - if (needsUpdate) - await canvas.AssignmentGroups.Update(courseCanvasId, correctGroup); - - return correctGroup; - } - ).ToArray()); - - return assignmentGroups; - } -} diff --git a/Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs b/Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs deleted file mode 100644 index 7a4b114..0000000 --- a/Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs +++ /dev/null @@ -1,259 +0,0 @@ -using System.Net.Quic; -using System.Reflection; -using System.Text.RegularExpressions; -using CanvasModel.Assignments; -using CanvasModel.Modules; -using CanvasModel.Quizzes; -using LocalModels; -using Management.Services.Canvas; - -namespace Management.Planner; - -public static partial class AssignmentSyncronizationExtensions -{ - internal static async Task SyncAssignmentToCanvas( - this LocalCourse localCourse, - ulong canvasCourseId, - LocalAssignment localAssignment, - IEnumerable canvasAssignments, - ICanvasService canvas - ) - { - var canvasAssignment = canvasAssignments.FirstOrDefault( - ca => ca.Name == localAssignment.Name - ); - - var canvasAssignmentGroupId = localAssignment.GetCanvasAssignmentGroupId(localCourse.Settings.AssignmentGroups); - - return canvasAssignment != null - ? await updateAssignmentIfNeeded( - localCourse, - canvasCourseId, - localAssignment, - canvasAssignment, - canvas, - canvasAssignmentGroupId - ) - : await canvas.Assignments.Create(canvasCourseId, localAssignment, canvasAssignmentGroupId); - } - - private static async Task updateAssignmentIfNeeded( - LocalCourse localCourse, - ulong canvasCourseId, - LocalAssignment localAssignment, - CanvasAssignment canvasAssignment, - ICanvasService canvas, - ulong? canvasAssignmentGroupId - ) - { - var assignmentNeedsUpdates = localAssignment.NeedsUpdates( - canvasAssignment, - canvasAssignmentGroupId, - quiet: false - ); - if (assignmentNeedsUpdates) - { - await canvas.Assignments.Update( - courseId: canvasCourseId, - canvasAssignmentId: canvasAssignment.Id, - localAssignment, - canvasAssignmentGroupId - ); - } - return canvasAssignment.Id; - } - - public static bool NeedsUpdates( - this LocalAssignment localAssignment, - CanvasAssignment canvasAssignment, - ulong? canvasAssignmentGroupId, - bool quiet = true - ) - { - var reason = localAssignment.GetUpdateReason(canvasAssignment, canvasAssignmentGroupId, quiet); - return reason != string.Empty; - } - - public static string GetUpdateReason( - this LocalAssignment localAssignment, - CanvasAssignment canvasAssignment, - ulong? canvasAssignmentGroupId, - bool quiet = true - ) - { - var canvasComparisonDueDate = - canvasAssignment.DueAt != null - ? new DateTime( - year: canvasAssignment.DueAt.Value.Year, - month: canvasAssignment.DueAt.Value.Month, - day: canvasAssignment.DueAt.Value.Day, - hour: canvasAssignment.DueAt.Value.Hour, - minute: canvasAssignment.DueAt.Value.Minute, - second: canvasAssignment.DueAt.Value.Second - ) - : new DateTime(); - var localComparisonDueDate = - canvasAssignment.DueAt != null - ? new DateTime( - year: localAssignment.DueAt.Year, - month: localAssignment.DueAt.Month, - day: localAssignment.DueAt.Day, - hour: localAssignment.DueAt.Hour, - minute: localAssignment.DueAt.Minute, - second: localAssignment.DueAt.Second - ) - : new DateTime(); - var dueDatesSame = canvasAssignment.DueAt != null && canvasComparisonDueDate == localComparisonDueDate; - if (!dueDatesSame) - { - var reason = $"Due dates different for assignment {localAssignment.Name}, local: {localAssignment.DueAt}, in canvas {canvasAssignment.DueAt}"; - if (!quiet) - { - Console.WriteLine(JsonSerializer.Serialize(canvasAssignment)); - Console.WriteLine(canvasComparisonDueDate); - Console.WriteLine(localComparisonDueDate); - Console.WriteLine(reason); - Console.WriteLine(JsonSerializer.Serialize(localAssignment.DueAt)); - Console.WriteLine(JsonSerializer.Serialize(canvasAssignment.DueAt)); - } - return reason; - } - - - DateTime? canvasComparisonLockDate = canvasAssignment.LockAt != null - ? new DateTime( - year: canvasAssignment.LockAt.Value.Year, - month: canvasAssignment.LockAt.Value.Month, - day: canvasAssignment.LockAt.Value.Day, - hour: canvasAssignment.LockAt.Value.Hour, - minute: canvasAssignment.LockAt.Value.Minute, - second: canvasAssignment.LockAt.Value.Second - ) - : null; - DateTime? localComparisonLockDate = localAssignment.LockAt != null - ? new DateTime( - year: localAssignment.LockAt.Value.Year, - month: localAssignment.LockAt.Value.Month, - day: localAssignment.LockAt.Value.Day, - hour: localAssignment.LockAt.Value.Hour, - minute: localAssignment.LockAt.Value.Minute, - second: localAssignment.LockAt.Value.Second - ) - : null; - - if (canvasComparisonLockDate != localComparisonLockDate) - { - var printableLocal = localComparisonLockDate?.ToString() ?? "null"; - var printableCanvas = canvasComparisonLockDate?.ToString() ?? "null"; - var reason = $"Lock dates different for assignment {localAssignment.Name}, local: {printableLocal}, in canvas {printableCanvas}"; - - if (!quiet) - { - // Console.WriteLine(JsonSerializer.Serialize(canvasAssignment)); - Console.WriteLine(canvasComparisonLockDate); - Console.WriteLine(localComparisonLockDate); - Console.WriteLine(reason); - Console.WriteLine(JsonSerializer.Serialize(localAssignment.LockAt)); - Console.WriteLine(JsonSerializer.Serialize(canvasAssignment.LockAt)); - } - return reason; - } - - - - var localHtmlDescription = removeHtmlDetails(localAssignment.GetDescriptionHtml()); - - var canvasHtmlDescription = canvasAssignment.Description; - canvasHtmlDescription = CanvasScriptTagRegex().Replace(canvasHtmlDescription, ""); - canvasHtmlDescription = CanvasLinkTagRegex().Replace(canvasHtmlDescription, ""); - canvasHtmlDescription = removeHtmlDetails(canvasHtmlDescription); - - var descriptionSame = canvasHtmlDescription == localHtmlDescription; - if (!descriptionSame) - { - var reason = $"descriptions different for {localAssignment.Name}"; - if (!quiet) - { - Console.WriteLine(); - Console.WriteLine(reason); - Console.WriteLine(); - - Console.WriteLine("Local Description:"); - Console.WriteLine(localHtmlDescription); - Console.WriteLine(); - Console.WriteLine("Canvas Description: "); - Console.WriteLine(canvasHtmlDescription); - Console.WriteLine(); - Console.WriteLine("Canvas Raw Description: "); - Console.WriteLine(canvasAssignment.Description); - Console.WriteLine(); - } - return reason; - } - - - if (canvasAssignment.Name != localAssignment.Name) - { - var reason = $"names different for {localAssignment.Name}, local: {localAssignment.Name}, in canvas {canvasAssignment.Name}"; - if (!quiet) - Console.WriteLine(reason); - return reason; - } - - - var pointsSame = canvasAssignment.PointsPossible == localAssignment.PointsPossible; - if (!pointsSame) - { - var reason = $"Points different for {localAssignment.Name}, local: {localAssignment.PointsPossible}, in canvas {canvasAssignment.PointsPossible}"; - if (!quiet) - Console.WriteLine(reason); - return reason; - } - - - var submissionTypesSame = canvasAssignment.SubmissionTypes.SequenceEqual( - localAssignment.SubmissionTypes.Select(t => t.ToString()) - ); - if (!submissionTypesSame) - { - var reason = $"Submission Types different for {localAssignment.Name}, local: {JsonSerializer.Serialize(localAssignment.SubmissionTypes.Select(t => t.ToString()))}, in canvas {JsonSerializer.Serialize(canvasAssignment.SubmissionTypes)}"; - if (!quiet) - Console.WriteLine(reason); - return reason; - } - - - var assignmentGroupSame = - canvasAssignmentGroupId != null - && canvasAssignmentGroupId == canvasAssignment.AssignmentGroupId; - if (!assignmentGroupSame) - { - var reason = $"Canvas assignment group ids different for {localAssignment.Name}, local: {canvasAssignmentGroupId}, in canvas {canvasAssignment.AssignmentGroupId}"; - if (!quiet) - Console.WriteLine(reason); - return reason; - } - - return string.Empty; - } - - private static string removeHtmlDetails(string canvasHtmlDescription) => canvasHtmlDescription - .Replace("
    ", "
    ") - .Replace("
    ", "
    ") - .Replace("
    ", "
    ") - .Replace("
    ", "
    ") - .Replace(">", "") - .Replace("<", "") - .Replace(">", "") - .Replace("<", "") - .Replace(""", "") - .Replace("\"", "") - .Replace("&", "") - .Replace("&", ""); - - [GeneratedRegex("")] - private static partial Regex CanvasScriptTagRegex(); - - [GeneratedRegex("]*>")] - private static partial Regex CanvasLinkTagRegex(); -} diff --git a/Management/Features/Configuration/Synchronization/ModuleSyncronizationExtensions.cs b/Management/Features/Configuration/Synchronization/ModuleSyncronizationExtensions.cs deleted file mode 100644 index 1b30db1..0000000 --- a/Management/Features/Configuration/Synchronization/ModuleSyncronizationExtensions.cs +++ /dev/null @@ -1,109 +0,0 @@ -using CanvasModel.Assignments; -using CanvasModel.Modules; -using LocalModels; -using Management.Services.Canvas; - -namespace Management.Planner; - -public static partial class ModuleSyncronizationExtensions -{ - - // internal static async Task SortCanvasModulesByLocalOrder( - // this LocalCourse localCourse, - // ulong canvasId, - // IEnumerable canvasModules, - // ICanvasService canvas - // ) - // { - // var currentCanvasPositions = canvasModules.ToDictionary(m => m.Id, m => m.Position); - // foreach (var (localModule, i) in localCourse.Modules.Select((m, i) => (m, i))) - // { - - // uint correctPosition = (uint)(i + 1); - // var canvasModule = canvasModules.FirstOrDefault(c => c.Name == localModule.Name) ?? throw new Exception($"error sorting canvas module, could not find canvas module with name {localModule.Name}"); ; - - // var currentCanvasPosition = currentCanvasPositions[canvasModule.Id]; - // if (currentCanvasPosition != correctPosition) - // { - // await canvas.Modules.UpdateModule(canvasId, canvasModule.Id, localModule.Name, correctPosition); - // } - // } - // } - - public static async Task SortModuleItems( - this LocalModule localModule, - ulong canvasId, - ulong moduleCanvasId, - ICanvasService canvas - ) - { - var canvasModuleItems = await canvas.Modules.GetModuleItems(canvasId, moduleCanvasId); - var moduleItemsInCorrectOrder = canvasModuleItems - .OrderBy(canvasItem => - { - - if (canvasItem.Type == "Page") - { - var localPage = localModule.Pages.FirstOrDefault(p => p.Name == canvasItem.Title); - Console.WriteLine(JsonSerializer.Serialize(localModule.Pages)); - - if (localPage != null) - return localPage.DueAt.Date; - } - return canvasItem.ContentDetails?.DueAt?.Date; - }) - .ThenBy(canvasItem => canvasItem.Title) - .Select((a, i) => (Item: a, Position: i + 1)) - .ToArray(); - - foreach (var (moduleItem, position) in moduleItemsInCorrectOrder) - { - var itemIsInCorrectOrder = moduleItem.Position == position; - - if (!itemIsInCorrectOrder) - { - await canvas.UpdateModuleItem( - canvasId, - moduleCanvasId, - moduleItem with - { - Position = position - } - ); - } - } - } - - internal static async Task EnsureAllModulesItemsCreated( - this LocalModule localModule, - ulong canvasId, - CanvasModule canvasModule, - Dictionary> canvasModulesItems, - ICanvasService canvas, - IEnumerable canvasAssignments - ) - { - var anyUpdated = false; - foreach (var localAssignment in localModule.Assignments.Where(a => a.DueAt > DateTime.Now)) - { - var canvasModuleItemContentNames = canvasModulesItems[canvasModule].Select(i => i.Title); - if (!canvasModuleItemContentNames.Contains(localAssignment.Name)) - { - var canvasAssignment = canvasAssignments.FirstOrDefault(a => a.Name == localAssignment.Name) - ?? throw new Exception($"cannot create module item if cannot find canvas assignment with name {localAssignment.Name}"); - - await canvas.CreateModuleItem( - canvasId, - canvasModule.Id, - localAssignment.Name, - "Assignment", - canvasAssignment.Id - ); - anyUpdated = true; - } - } - - return anyUpdated; - } - -} diff --git a/Management/Features/Configuration/Synchronization/PageSynchronizationExtension.cs b/Management/Features/Configuration/Synchronization/PageSynchronizationExtension.cs deleted file mode 100644 index be8c7be..0000000 --- a/Management/Features/Configuration/Synchronization/PageSynchronizationExtension.cs +++ /dev/null @@ -1,23 +0,0 @@ -using CanvasModel.Pages; -using LocalModels; -using Management.Services.Canvas; - -public static class PageSynchronizationExtension -{ - public static async Task AddPageToCanvas( - this LocalCourse localCourse, - LocalCoursePage localPage, - ICanvasService canvas - ) - { - if (localCourse.Settings.CanvasId == null) - { - Console.WriteLine("Cannot add page to canvas without canvas course id"); - return null; - } - ulong courseCanvasId = (ulong)localCourse.Settings.CanvasId; - - var canvasPage = await canvas.Pages.Create(courseCanvasId, localPage); - return canvasPage; - } -} diff --git a/Management/Features/Configuration/Synchronization/QuizSyncronizationExtensions.cs b/Management/Features/Configuration/Synchronization/QuizSyncronizationExtensions.cs deleted file mode 100644 index cb77d8f..0000000 --- a/Management/Features/Configuration/Synchronization/QuizSyncronizationExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Text.RegularExpressions; -using CanvasModel.Assignments; -using CanvasModel.Modules; -using CanvasModel.Quizzes; -using LocalModels; -using Management.Services.Canvas; - -namespace Management.Planner; - -public static partial class QuizSyncronizationExtensions -{ - public static bool QuizIsCreated(this LocalQuiz localQuiz, IEnumerable canvasQuizzes) - { - return canvasQuizzes.Any(q => q.Title == localQuiz.Name); - } - - public static async Task AddQuizToCanvas( - this LocalCourse localCourse, - LocalQuiz localQuiz, - ICanvasService canvas - ) - { - if (localCourse.Settings.CanvasId == null) - { - Console.WriteLine("Cannot add quiz to canvas without canvas course id"); - return null; - } - ulong courseCanvasId = (ulong)localCourse.Settings.CanvasId; - - var canvasAssignmentGroupId = localQuiz.GetCanvasAssignmentGroupId(localCourse.Settings.AssignmentGroups); - - var canvasQuizId = await canvas.Quizzes.Create(courseCanvasId, localQuiz, canvasAssignmentGroupId); - return canvasQuizId; - } -} diff --git a/Management/GlobalUsings.cs b/Management/GlobalUsings.cs deleted file mode 100644 index 4dad470..0000000 --- a/Management/GlobalUsings.cs +++ /dev/null @@ -1,3 +0,0 @@ -global using System.Text.Json; -global using System.Text.Json.Serialization; -global using Microsoft.Extensions.Logging; diff --git a/Management/Management.csproj b/Management/Management.csproj deleted file mode 100644 index 2415ae6..0000000 --- a/Management/Management.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - - - - - - - - - diff --git a/Management/Models/CanvasModels/Assignments/CanvasAssignment.cs b/Management/Models/CanvasModels/Assignments/CanvasAssignment.cs deleted file mode 100644 index c6a7c29..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasAssignment.cs +++ /dev/null @@ -1,211 +0,0 @@ -using CanvasModel.Discussions; -using CanvasModel.Submissions; - -namespace CanvasModel.Assignments; - -public record CanvasAssignment -( - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("name")] - string Name, - - [property: JsonPropertyName("description")] - string Description, - - [property: JsonPropertyName("created_at")] - DateTime CreatedAt, - - [property: JsonPropertyName("has_overrides")] - bool HasOverrides, - - [property: JsonPropertyName("course_id")] - ulong CourseId, - - [property: JsonPropertyName("html_url")] - string HtmlUrl, - - [property: JsonPropertyName("submissions_download_url")] - string SubmissionsDownloadUrl, - - [property: JsonPropertyName("assignment_group_id")] - ulong AssignmentGroupId, - - [property: JsonPropertyName("due_date_required")] - bool DueDateRequired, - - [property: JsonPropertyName("max_name_length")] - uint MaxNameLength, - - [property: JsonPropertyName("peer_reviews")] - bool PeerReviews, - - [property: JsonPropertyName("automatic_peer_reviews")] - bool AutomaticPeerReviews, - - [property: JsonPropertyName("position")] - ulong Position, - - [property: JsonPropertyName("grading_type")] - string GradingType, - - [property: JsonPropertyName("published")] - bool Published, - - [property: JsonPropertyName("unpublishable")] - bool Unpublishable, - - [property: JsonPropertyName("only_visible_to_overrides")] - bool OnlyVisibleToOverrides, - - [property: JsonPropertyName("locked_for_user")] - bool LockedForUser, - - [property: JsonPropertyName("moderated_grading")] - bool ModeratedGrading, - - [property: JsonPropertyName("grader_count")] - uint GraderCount, - - [property: JsonPropertyName("allowed_attempts")] - int AllowedAttempts, - - [property: JsonPropertyName("is_quiz_assignment")] - bool IsQuizAssignment, - - [property: JsonPropertyName("submission_types")] - IEnumerable SubmissionTypes, - - [property: JsonPropertyName("updated_at")] - DateTime? UpdatedAt = null, - - [property: JsonPropertyName("due_at")] - DateTime? DueAt = null, - - [property: JsonPropertyName("lock_at")] - DateTime? LockAt = null, - - [property: JsonPropertyName("unlock_at")] - DateTime? UnlockAt = null, - - [property: JsonPropertyName("all_dates")] - IEnumerable? AllDates = null, - - [property: JsonPropertyName("allowed_extensions")] - IEnumerable? AllowedExtensions = null, - - [property: JsonPropertyName("turnitin_enabled")] - bool? TurnitinEnabled = null, - - [property: JsonPropertyName("vericite_enabled")] - bool? VeriCiteEnabled = null, - - [property: JsonPropertyName("turnitin_settings")] - CanvasTurnitinSettings? TurnitinSettings = null, - - [property: JsonPropertyName("grade_group_students_individually")] - bool? GradeGroupStudentsIndividually = null, - - [property: JsonPropertyName("external_tool_tag_attributes")] - CanvasExternalToolTagAttributes? ExternalToolTagAttributes = null, - - [property: JsonPropertyName("peer_review_count")] - uint? PeerReviewCount = null, - - [property: JsonPropertyName("peer_reviews_assign_at")] - DateTime? PeerReviewsAssignAt = null, - - [property: JsonPropertyName("intra_group_peer_reviews")] - bool? IntraGroupPeerReviews = null, - - [property: JsonPropertyName("group_category_id")] - ulong? GroupCategoryId = null, - - [property: JsonPropertyName("needs_grading_count")] - uint? NeedsGradingCount = null, - - [property: JsonPropertyName("needs_grading_count_be_section")] - IEnumerable? NeedsGradingCountBySection = null, - - [property: JsonPropertyName("post_to_sis")] - bool? PostToSis = null, - - [property: JsonPropertyName("integration_id")] - string? IntegrationId = null, - - [property: JsonPropertyName("integration_data")] - object? IntegrationData = null, - - [property: JsonPropertyName("muted")] - bool? Muted = null, - - [property: JsonPropertyName("points_possible")] - double? PointsPossible = null, - - [property: JsonPropertyName("has_submitted_submissions")] - bool? HasSubmittedSubmissions = null, - - [property: JsonPropertyName("grading_standard_id")] - ulong? GradingStandardId = null, - - [property: JsonPropertyName("lock_info")] - CanvasLockInfo? LockInfo = null, - - [property: JsonPropertyName("lock_explanation")] - string? LockExplanation = null, - - [property: JsonPropertyName("quiz_id")] - ulong? QuizId = null, - - [property: JsonPropertyName("anonymous_submissions")] - bool? AnonymousSubmissions = null, - - [property: JsonPropertyName("discussion_topic")] - DiscussionTopicModel? DiscussionTopic = null, - - [property: JsonPropertyName("freeze_on_copy")] - bool? FreezeOnCopy = null, - - [property: JsonPropertyName("frozen")] - bool? Frozen = null, - - [property: JsonPropertyName("frozen_attributes")] - IEnumerable? FrozenAttributes = null, - - [property: JsonPropertyName("submission")] - SubmissionModel? Submission = null, - - [property: JsonPropertyName("use_rubric_for_grading")] - bool? UseRubricForGrading = null, - - [property: JsonPropertyName("rubric_settings")] - object? RubricSettings = null, - - [property: JsonPropertyName("rubric")] - IEnumerable? Rubric = null, - - [property: JsonPropertyName("assignment_visibility")] - IEnumerable? AssignmentVisibility = null, - - [property: JsonPropertyName("overrides")] - IEnumerable? Overrides = null, - - [property: JsonPropertyName("omit_from_final_grade")] - bool? OmitFromFinalGrade = null, - - [property: JsonPropertyName("final_grader_id")] - ulong? FinalGraderId = null, - - [property: JsonPropertyName("grader_comments_visible_to_graders")] - bool? GraderCommentsVisibleToGraders = null, - - [property: JsonPropertyName("graders_anonymous_to_graders")] - bool? GradersAnonymousToGraders = null, - - [property: JsonPropertyName("grader_names_anonymous_to_final_grader")] - bool? GraderNamesVisibleToFinalGrader = null, - - [property: JsonPropertyName("anonymous_grading")] - bool? AnonymousGrading = null -); diff --git a/Management/Models/CanvasModels/Assignments/CanvasAssignmentDate.cs b/Management/Models/CanvasModels/Assignments/CanvasAssignmentDate.cs deleted file mode 100644 index e7527a4..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasAssignmentDate.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasAssignmentDate -( - [property: JsonPropertyName("title")] - string Title, - - [property: JsonPropertyName("id")] - ulong? Id = null, - - [property: JsonPropertyName("base")] - bool? Base = null, - - [property: JsonPropertyName("due_at")] - DateTime? DueAt = null, - - [property: JsonPropertyName("unlock_at")] - DateTime? UnlockAt = null, - - [property: JsonPropertyName("lock_at")] - DateTime? LockAt = null -); diff --git a/Management/Models/CanvasModels/Assignments/CanvasAssignmentGroup.cs b/Management/Models/CanvasModels/Assignments/CanvasAssignmentGroup.cs deleted file mode 100644 index 015ba70..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasAssignmentGroup.cs +++ /dev/null @@ -1,29 +0,0 @@ - -namespace CanvasModel.Assignments; - -public record CanvasAssignmentGroup -{ - [JsonPropertyName("id")] - public ulong Id { get; init; } - - [JsonPropertyName("name")] - public required string Name { get; init; } - - [JsonPropertyName("position")] - public int Position { get; init; } - - [JsonPropertyName("group_weight")] - public double GroupWeight { get; init; } - - // [JsonPropertyName("sis_source_id")] - // public string? SisSourceId { get; init; } = null; - - // [JsonPropertyName("integration_data")] - // public Dictionary IntegrationData { get; init; } = new Dictionary(); - - // [JsonPropertyName("assignments")] - // public List Assignments { get; init; } - - // [JsonPropertyName("rules")] - // public object Rules { get; init; } // The specific type for 'Rules' is not detailed in the spec, so using object for now. -} diff --git a/Management/Models/CanvasModels/Assignments/CanvasAssignmentOverride.cs b/Management/Models/CanvasModels/Assignments/CanvasAssignmentOverride.cs deleted file mode 100644 index 1160ba4..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasAssignmentOverride.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasAssignmentOverride -( - - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("assignment_id")] - ulong AssignmentId, - - [property: JsonPropertyName("course_section_ids")] - ulong CourseSectionId, - - [property: JsonPropertyName("title")] - string Title, - - [property: JsonPropertyName("student_ids")] - IEnumerable? StudentIds = null, - - [property: JsonPropertyName("group_id")] - ulong? GroupId = null, - - [property: JsonPropertyName("due_at")] - DateTime? DueAt = null, - - [property: JsonPropertyName("all_day")] - bool? AllDay = null, - - [property: JsonPropertyName("all_day_date")] - DateTime? AllDayDate = null, - - [property: JsonPropertyName("unlock_at")] - DateTime? UnlockAt = null, - - [property: JsonPropertyName("lock_at")] - DateTime? LockAt = null -); diff --git a/Management/Models/CanvasModels/Assignments/CanvasExternalToolTagAttributes.cs b/Management/Models/CanvasModels/Assignments/CanvasExternalToolTagAttributes.cs deleted file mode 100644 index ab255df..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasExternalToolTagAttributes.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasExternalToolTagAttributes -( - [property: JsonPropertyName("url")] - string Url, - - [property: JsonPropertyName("resource_link_id")] - string ResourceLinkId, - - [property: JsonPropertyName("new_tab")] - bool? NewTab = null -); diff --git a/Management/Models/CanvasModels/Assignments/CanvasLockInfo.cs b/Management/Models/CanvasModels/Assignments/CanvasLockInfo.cs deleted file mode 100644 index ffb422f..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasLockInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasLockInfo -( - [property: JsonPropertyName("asset_string")] - string AssetString, - - [property: JsonPropertyName("unlock_at")] - DateTime? UnlockAt = null, - - [property: JsonPropertyName("lock_at")] - DateTime? LockAt = null, - - [property: JsonPropertyName("context_module")] - object? ContextModule = null, - - [property: JsonPropertyName("manually_locked")] - bool? ManuallyLocked = null -); diff --git a/Management/Models/CanvasModels/Assignments/CanvasNeedsGradingCount.cs b/Management/Models/CanvasModels/Assignments/CanvasNeedsGradingCount.cs deleted file mode 100644 index caabe97..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasNeedsGradingCount.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasNeedsGradingCount -( - [property: JsonPropertyName("section_id")] - string SectionId, - - [property: JsonPropertyName("needs_grading_count")] - uint NeedsGradingCount -); diff --git a/Management/Models/CanvasModels/Assignments/CanvasRubric.cs b/Management/Models/CanvasModels/Assignments/CanvasRubric.cs deleted file mode 100644 index 48d1bc1..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasRubric.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasRubric -{ - [JsonPropertyName("id")] - public ulong? Id { get; set; } - - [JsonPropertyName("title")] - public required string Title { get; set; } - - [JsonPropertyName("context_id")] - public ulong ContextId { get; set; } - - [JsonPropertyName("context_type")] - public required string ContextType { get; set; } - - [JsonPropertyName("points_possible")] - public double PointsPossible { get; set; } - - [JsonPropertyName("reusable")] - public bool Reusable { get; set; } - - [JsonPropertyName("read_only")] - public bool ReadOnly { get; set; } - - // [JsonPropertyName("free_form_criterion_comments")] - // public bool? FreeFormCriterionComments { get; set; } - - [JsonPropertyName("hide_score_total")] - public bool? HideScoreTotal { get; set; } - - // [JsonPropertyName("data")] - // public required IEnumerable Data { get; set; } - - // assessments - // associations -} diff --git a/Management/Models/CanvasModels/Assignments/CanvasRubricAssociation.cs b/Management/Models/CanvasModels/Assignments/CanvasRubricAssociation.cs deleted file mode 100644 index ce51488..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasRubricAssociation.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasRubricAssociation -{ - [JsonPropertyName("id")] - public ulong Id { get; set; } - - [JsonPropertyName("rubrid_id")] - public ulong RubricId { get; set; } - - [JsonPropertyName("association_id")] - public ulong AssociationId { get; set; } - - [JsonPropertyName("association_type")] - public required string AssociationType { get; set; } - - [JsonPropertyName("use_for_grading")] - public bool UseForGrading { get; set; } - - [JsonPropertyName("summary_data")] - public string? SaummaryDaata { get; set; } - - [JsonPropertyName("purpose")] - public required string Purpose { get; set; } - - [JsonPropertyName("hide_score_total")] - public bool? HideScoreTotal { get; set; } - - [JsonPropertyName("hide_points")] - public bool HidePoints { get; set; } - - [JsonPropertyName("hide_outcome-results")] - public bool HideOUtcomeResult { get; set; } - -} diff --git a/Management/Models/CanvasModels/Assignments/CanvasRubricCriteria.cs b/Management/Models/CanvasModels/Assignments/CanvasRubricCriteria.cs deleted file mode 100644 index c961de8..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasRubricCriteria.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasRubricCriteria -( - [property: JsonPropertyName("id")] - string Id, - - [property: JsonPropertyName("description")] - string Description, - - [property: JsonPropertyName("long_description")] - string LongDescription, - - [property: JsonPropertyName("points")] - double? Points, - [property: JsonPropertyName("learning_outcome_id")] - string? LearningOutcomeId, - - [property: JsonPropertyName("vendor_guid")] - string? VendorGuid, - - [property: JsonPropertyName("criterion_use_range")] - bool? CriterionUseRange = null, - - [property: JsonPropertyName("ratings")] - IEnumerable? Ratings = null, - - [property: JsonPropertyName("ignore_for_scoring")] - bool? IgnoreForScoring = null -); diff --git a/Management/Models/CanvasModels/Assignments/CanvasRubricRating.cs b/Management/Models/CanvasModels/Assignments/CanvasRubricRating.cs deleted file mode 100644 index 6ed8970..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasRubricRating.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasRubricRating -( - [property: JsonPropertyName("points")] - double Points, - - [property: JsonPropertyName("id")] - string Id, - - [property: JsonPropertyName("description")] - string Description, - - [property: JsonPropertyName("long_description")] - string LongDescription -); diff --git a/Management/Models/CanvasModels/Assignments/CanvasTurnitinSettings.cs b/Management/Models/CanvasModels/Assignments/CanvasTurnitinSettings.cs deleted file mode 100644 index e0c73f7..0000000 --- a/Management/Models/CanvasModels/Assignments/CanvasTurnitinSettings.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace CanvasModel.Assignments; - -public record CanvasTurnitinSettings -( - [property: JsonPropertyName("originality_report_visibility")] - string OriginalityReportVisibility, - - [property: JsonPropertyName("s_paper_check")] - bool SPaperCheck, - - [property: JsonPropertyName("internet_check")] - bool InternetCheck, - - [property: JsonPropertyName("journal_check")] - bool JournalCheck, - - [property: JsonPropertyName("exclude_biblio")] - bool ExcludeBiblio, - - [property: JsonPropertyName("exclude_quoted")] - bool ExcludeQuoted, - - [property: JsonPropertyName("exclude_small_matches_type")] - bool? ExcludeSmallMatchesType = null, - - [property: JsonPropertyName("exclude_small_matches_value")] - uint? ExcludeSmallMatchesValue = null -); diff --git a/Management/Models/CanvasModels/Courses/CalendarLinkModel.cs b/Management/Models/CanvasModels/Courses/CalendarLinkModel.cs deleted file mode 100644 index 1a0635e..0000000 --- a/Management/Models/CanvasModels/Courses/CalendarLinkModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace CanvasModel.Courses; -public record CalendarLinkModel -( - [property: JsonPropertyName("ics")] string Ics -); diff --git a/Management/Models/CanvasModels/Courses/CourseModel.cs b/Management/Models/CanvasModels/Courses/CourseModel.cs deleted file mode 100644 index ce71973..0000000 --- a/Management/Models/CanvasModels/Courses/CourseModel.cs +++ /dev/null @@ -1,59 +0,0 @@ -using CanvasModel.Enrollments; - -namespace CanvasModel.Courses; - -public record CourseModel( - [property: JsonPropertyName("id")] ulong Id, - [property: JsonPropertyName("sis_course_id")] string SisCourseId, - [property: JsonPropertyName("uuid")] string Uuid, - [property: JsonPropertyName("integration_id")] string IntegrationId, - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("course_code")] string CourseCode, - [property: JsonPropertyName("workflow_state")] string WorkflowState, - [property: JsonPropertyName("account_id")] ulong AccountId, - [property: JsonPropertyName("root_account_id")] ulong RootAccountId, - [property: JsonPropertyName("enrollment_term_id")] ulong EnrollmentTermId, - [property: JsonPropertyName("created_at")] DateTime CreatedAt, - [property: JsonPropertyName("locale")] string Locale, - [property: JsonPropertyName("calendar")] CalendarLinkModel Calendar, - [property: JsonPropertyName("default_view")] string DefaultView, - [property: JsonPropertyName("syllabus_body")] string SyllabusBody, - [property: JsonPropertyName("permissions")] Dictionary Permissions, - [property: JsonPropertyName("storage_quota_mb")] ulong StorageQuotaMb, - [property: JsonPropertyName("storage_quota_used_mb")] ulong StorageQuotaUsedMb, - [property: JsonPropertyName("license")] string License, - [property: JsonPropertyName("course_format")] string CourseFormat, - [property: JsonPropertyName("time_zone")] string TimeZone, - [property: JsonPropertyName("sis_import_id")] ulong? SisImportId = null, - [property: JsonPropertyName("grading_standard_id")] ulong? GradingStandardId = null, - [property: JsonPropertyName("start_at")] DateTime? StartAt = null, - [property: JsonPropertyName("end_at")] DateTime? EndAt = null, - [property: JsonPropertyName("enrollments")] IEnumerable? Enrollments = null, - [property: JsonPropertyName("total_students")] ulong? TotalStudents = null, - [property: JsonPropertyName("needs_grading_count")] uint? NeedsGradingCount = null, - [property: JsonPropertyName("term")] TermModel? Term = null, - [property: JsonPropertyName("course_progress")] CourseProgressModel? CourseProgress = null, - [property: JsonPropertyName("apply_assignment_group_weights")] - bool? ApplyAssignmentGroupWeights = null, - [property: JsonPropertyName("is_public")] bool? Is = null, - [property: JsonPropertyName("is_public_to_auth_users")] bool? IsPublicToAuthUsers = null, - [property: JsonPropertyName("public_syllabus")] bool? PublicSyllabus = null, - [property: JsonPropertyName("public_syllabus_to_auth")] bool? PublicSyllabusToAuth = null, - [property: JsonPropertyName("public_description")] string? PublicDescription = null, - [property: JsonPropertyName("hide_final_grades")] bool? HideFinalGrades = null, - [property: JsonPropertyName("allow_student_assignment_edits")] - bool? AllowStudentAssignmentEdits = null, - [property: JsonPropertyName("allow_wiki_comments")] bool? AllowWikiComments = null, - [property: JsonPropertyName("allow_student_forum_attachments")] - bool? AllowStudentForumAttachments = null, - [property: JsonPropertyName("open_enrollment")] bool? OpenEnrollment = null, - [property: JsonPropertyName("self_enrollment")] bool? SelfEnrollment = null, - [property: JsonPropertyName("restrict_enrollments_to_courses")] - bool? RestrictEnrollmentsToCourseDates = null, - [property: JsonPropertyName("access_restricted_by_date")] bool? AccessRestrictedByDate = null, - [property: JsonPropertyName("blueprint")] bool? Blueprint = null, - [property: JsonPropertyName("blueprint_restrictions")] - Dictionary? BlueprintRestrictions = null, - [property: JsonPropertyName("blueprint_restrictions_by_object_type")] - Dictionary>? BlueprintRestrictionsByObjectType = null -); diff --git a/Management/Models/CanvasModels/Courses/CourseProgressModel.cs b/Management/Models/CanvasModels/Courses/CourseProgressModel.cs deleted file mode 100644 index 3e4067a..0000000 --- a/Management/Models/CanvasModels/Courses/CourseProgressModel.cs +++ /dev/null @@ -1,16 +0,0 @@ - -namespace CanvasModel.Courses; -public record CourseProgressModel -( - [property: JsonPropertyName("requirement_count")] - uint? RequirementCount = null, - - [property: JsonPropertyName("requirement_completed_count")] - uint? RequirementCompletedCount = null, - - [property: JsonPropertyName("next_requirement_url")] - string? NextRequirementUrl = null, - - [property: JsonPropertyName("completed_at")] - DateTime? CompletedAt = null -); diff --git a/Management/Models/CanvasModels/Courses/CourseSettingsModel.cs b/Management/Models/CanvasModels/Courses/CourseSettingsModel.cs deleted file mode 100644 index cb390af..0000000 --- a/Management/Models/CanvasModels/Courses/CourseSettingsModel.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace CanvasModel.Courses; -public record CourseSettingsModel -( - - [property: JsonPropertyName("allow_final_grade_override")] - bool AllowFinalGradeOverride, - - [property: JsonPropertyName("allow_student_discussion_topics")] - bool AllowStudentDiscussionTopics, - - [property: JsonPropertyName("allow_student_forum_attachments")] - bool AllowStudentForumAttachments, - - [property: JsonPropertyName("allow_student_discussion_editing")] - bool AllowStudentDiscussionEditing, - - [property: JsonPropertyName("grading_standard_enabled")] - bool GradingStandardEnabled, - - [property: JsonPropertyName("allow_student_organized_groups")] - bool AllowStudentOrganizedGroups, - - [property: JsonPropertyName("hide_final_groups")] - bool HideFinalGrades, - - [property: JsonPropertyName("hide_distributor_graphs")] - bool HideDistributionGraphs, - - [property: JsonPropertyName("lock_all_announcements")] - bool LockAllAnnouncements, - - [property: JsonPropertyName("restrict_student_past_view")] - bool RestrictStudentPastView, - - [property: JsonPropertyName("restrict_student_future_view")] - bool RestrictStudentFutureView, - - [property: JsonPropertyName("show_announcements_on_home_page")] - bool ShowAnnouncementsOnHomePage, - - [property: JsonPropertyName("home_page_announcements_limit")] - long HomePageAnnouncementLimit, - - [property: JsonPropertyName("grading_standard_id")] - ulong? GradingStandardId = null -); diff --git a/Management/Models/CanvasModels/Courses/ShortCourseModel.cs b/Management/Models/CanvasModels/Courses/ShortCourseModel.cs deleted file mode 100644 index 90b051c..0000000 --- a/Management/Models/CanvasModels/Courses/ShortCourseModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace CanvasModel.Courses; -public record ShortCourseModel -( - [property: JsonPropertyName("id")] ulong Id, - [property: JsonPropertyName("name")] string Name -); diff --git a/Management/Models/CanvasModels/Courses/TermModel.cs b/Management/Models/CanvasModels/Courses/TermModel.cs deleted file mode 100644 index 7df6023..0000000 --- a/Management/Models/CanvasModels/Courses/TermModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace CanvasModel.Courses; -public record TermModel -( - [property: JsonPropertyName("id")] ulong Id, - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("start_at")] DateTime? StartAt = null, - [property: JsonPropertyName("end_at")] DateTime? EndAt = null -); diff --git a/Management/Models/CanvasModels/Discussions/DiscussionTopicModel.cs b/Management/Models/CanvasModels/Discussions/DiscussionTopicModel.cs deleted file mode 100644 index 4f6fe7d..0000000 --- a/Management/Models/CanvasModels/Discussions/DiscussionTopicModel.cs +++ /dev/null @@ -1,111 +0,0 @@ -using CanvasModel.Users; - -namespace CanvasModel.Discussions; - -public record DiscussionTopicModel -( - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("title")] - string Title, - - [property: JsonPropertyName("message")] - string Message, - - [property: JsonPropertyName("html_url")] - string HtmlUrl, - - [property: JsonPropertyName("read_state")] - string ReadState, - - [property: JsonPropertyName("subscription_hold")] - string SubscriptionHold, - - [property: JsonPropertyName("assignment_id")] - int AssignmentId, - - [property: JsonPropertyName("lock_explanation")] - string LockExplanation, - - [property: JsonPropertyName("user_name")] - string UserName, - - [property: JsonPropertyName("topic_children")] - IEnumerable TopicChildren, - - [property: JsonPropertyName("podcast_url")] - string PodcastUrl, - - [property: JsonPropertyName("discussion_type")] - string DiscussionType, - - [property: JsonPropertyName("attachments")] - IEnumerable Attachments, - - [property: JsonPropertyName("permissions")] - Dictionary Permissions, - - [property: JsonPropertyName("author")] - UserDisplayModel Author, - - [property: JsonPropertyName("unread_count")] - uint? UnreadCount = null, - - [property: JsonPropertyName("subscribed")] - bool? Subscribed = null, - - [property: JsonPropertyName("posted_at")] - DateTime? PostedAt = null, - - [property: JsonPropertyName("last_reply_at")] - DateTime? LastReplyAt = null, - - [property: JsonPropertyName("require_initial_post")] - bool? RequireInitialPost = null, - - [property: JsonPropertyName("user_can_see_posts")] - bool? UserCanSeePosts = null, - - [property: JsonPropertyName("discussion_subentry_count")] - uint? DiscussionSubentryCount = null, - - [property: JsonPropertyName("delayed_post_at")] - DateTime? DelayedPostAt = null, - - [property: JsonPropertyName("published")] - bool? Published = null, - - [property: JsonPropertyName("lock_at")] - DateTime? LockAt = null, - - [property: JsonPropertyName("locked")] - bool? Locked = null, - - [property: JsonPropertyName("pinned")] - bool? Pinned = null, - - [property: JsonPropertyName("locked_for_user")] - bool? LockedForUser = null, - - [property: JsonPropertyName("lock_info")] - object? LockInfo = null, - - [property: JsonPropertyName("group_topic_children")] - object? GroupTopicChildren = null, - - [property: JsonPropertyName("root_topic_id")] - ulong? RootTopicId = null, - - [property: JsonPropertyName("group_category_id")] - ulong? GroupCategoryId = null, - - [property: JsonPropertyName("allow_rating")] - bool? AllowRating = null, - - [property: JsonPropertyName("only_graders_can_rate")] - bool? OnlyGradersCanRate = null, - - [property: JsonPropertyName("sort_by_rating")] - bool? SortByRating = null -); diff --git a/Management/Models/CanvasModels/Discussions/FileAttachmentModel.cs b/Management/Models/CanvasModels/Discussions/FileAttachmentModel.cs deleted file mode 100644 index b196490..0000000 --- a/Management/Models/CanvasModels/Discussions/FileAttachmentModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CanvasModel.Discussions; - -public record FileAttachmentModel -( - [property: JsonPropertyName("content_type")] - string ContentType, - - [property: JsonPropertyName("url")] - string Url, - - [property: JsonPropertyName("filename")] - string Filename, - - [property: JsonPropertyName("display_name")] - string DisplayName -); diff --git a/Management/Models/CanvasModels/Discussions/TopicEntryModel.cs b/Management/Models/CanvasModels/Discussions/TopicEntryModel.cs deleted file mode 100644 index 9d1a814..0000000 --- a/Management/Models/CanvasModels/Discussions/TopicEntryModel.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace CanvasModel.Discussions; - -public record TopicEntryModel -( - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("user_id")] - ulong UserId, - - [property: JsonPropertyName("user_name")] - string UserName, - - [property: JsonPropertyName("message")] - string Message, - - [property: JsonPropertyName("read_state")] - string ReadState, - - [property: JsonPropertyName("forced_read_state")] - bool ForcedReadState, - - [property: JsonPropertyName("created_at")] - DateTime CreatedAt, - - [property: JsonPropertyName("editor_id")] - ulong? EditorId = null, - - [property: JsonPropertyName("updated_at")] - DateTime? UpdatedAt = null, - - [property: JsonPropertyName("attachment")] - FileAttachmentModel? Attachment = null, - - [property: JsonPropertyName("recent_replies")] - IEnumerable? RecentReplies = null, - - [property: JsonPropertyName("has_more_replies")] - bool? HasMoreReplies = null -); diff --git a/Management/Models/CanvasModels/Discussions/TopicReplyModel.cs b/Management/Models/CanvasModels/Discussions/TopicReplyModel.cs deleted file mode 100644 index d1a6d91..0000000 --- a/Management/Models/CanvasModels/Discussions/TopicReplyModel.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace CanvasModel.Discussions; - -public record TopicReplyModel -( - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("user_id")] - ulong UserId, - - [property: JsonPropertyName("user_name")] - string UserName, - - [property: JsonPropertyName("message")] - string Message, - - [property: JsonPropertyName("read_state")] - string ReadState, - - [property: JsonPropertyName("created_at")] - DateTime CreatedAt, - - [property: JsonPropertyName("editor_id")] - ulong? EditorId = null, - - [property: JsonPropertyName("forced_read_state")] - bool? ForcedReadState = null -); diff --git a/Management/Models/CanvasModels/EnrollmentTerms/EnrollmentTermModel.cs b/Management/Models/CanvasModels/EnrollmentTerms/EnrollmentTermModel.cs deleted file mode 100644 index 2858bf7..0000000 --- a/Management/Models/CanvasModels/EnrollmentTerms/EnrollmentTermModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace CanvasModel.EnrollmentTerms; - -public record EnrollmentTermModel -( - [property: JsonPropertyName("id")] ulong Id, - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("sis_term_id")] string? SisTermId = null, - [property: JsonPropertyName("sis_import_id")] ulong? SisImportId = null, - [property: JsonPropertyName("start_at")] DateTime? StartAt = null, - [property: JsonPropertyName("end_at")] DateTime? EndAt = null, - [property: JsonPropertyName("grading_period_group_id")] ulong? GradingPeriodGroupId = null, - [property: JsonPropertyName("workflow_state")] string? WorkflowState = null, - [property: JsonPropertyName("overrides")] - Dictionary? Overrides = null -); - -public record EnrollmentTermDateOverrideModel -( - [property: JsonPropertyName("start_at")] DateTime? StartAt = null, - [property: JsonPropertyName("end_at")] DateTime? EndAt = null -); diff --git a/Management/Models/CanvasModels/EnrollmentTerms/RedundantEnrollmentTermsResponse.cs b/Management/Models/CanvasModels/EnrollmentTerms/RedundantEnrollmentTermsResponse.cs deleted file mode 100644 index 7850973..0000000 --- a/Management/Models/CanvasModels/EnrollmentTerms/RedundantEnrollmentTermsResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CanvasModel.EnrollmentTerms; - -public record RedundantEnrollmentTermsResponse -( - [property: JsonPropertyName("enrollment_terms")] - IEnumerable EnrollmentTerms -); diff --git a/Management/Models/CanvasModels/Enrollments/EnrollmentModel.cs b/Management/Models/CanvasModels/Enrollments/EnrollmentModel.cs deleted file mode 100644 index 3e1aeb2..0000000 --- a/Management/Models/CanvasModels/Enrollments/EnrollmentModel.cs +++ /dev/null @@ -1,135 +0,0 @@ -using CanvasModel.Users; - -namespace CanvasModel.Enrollments; -public record EnrollmentModel -( - - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("course_id")] - ulong CourseId, - - [property: JsonPropertyName("enrollment_state")] - string EnrollmentState, - - [property: JsonPropertyName("type")] - string Type, - - [property: JsonPropertyName("user_id")] - ulong UserId, - - [property: JsonPropertyName("role")] - string Role, - - [property: JsonPropertyName("role_id")] - ulong RoleId, - - [property: JsonPropertyName("html_url")] - string HtmlUrl, - - [property: JsonPropertyName("grades")] - GradeModel Grades, - - [property: JsonPropertyName("user")] - UserDisplayModel User, - - [property: JsonPropertyName("override_grade")] - string OverrideGrade, - - [property: JsonPropertyName("sis_course_id")] - string? SisCourseId = null, - - [property: JsonPropertyName("course_integration_id")] - string? CourseIntegrationId = null, - - [property: JsonPropertyName("course_section_id")] - ulong? CourseSectionId = null, - - [property: JsonPropertyName("section_integration_id")] - string? SectionIntegrationId = null, - - [property: JsonPropertyName("sis_account_id")] - string? SisAccountId = null, - - [property: JsonPropertyName("sis_section_id")] - string? SisSectionId = null, - - [property: JsonPropertyName("sis_user_id")] - string? SisUserId = null, - - [property: JsonPropertyName("limit_privileges_to_course_section")] - bool? LimitPrivilegesToCourseSection = null, - - [property: JsonPropertyName("sis_import_id")] - ulong? SisImportId = null, - - [property: JsonPropertyName("root_account_id")] - ulong? RootAccountId = null, - - [property: JsonPropertyName("associated_user_id")] - ulong? AssociatedUserId = null, - - [property: JsonPropertyName("created_at")] - DateTime? CreatedAt = null, - - [property: JsonPropertyName("updated_at")] - DateTime? UpdatedAt = null, - - [property: JsonPropertyName("start_at")] - DateTime? StartAt = null, - - [property: JsonPropertyName("end_at")] - DateTime? EndAt = null, - - [property: JsonPropertyName("last_activity_at")] - DateTime? LastActivityAt = null, - - [property: JsonPropertyName("last_attended_at")] - DateTime? LastAttendedAt = null, - - [property: JsonPropertyName("total_activity_time")] - ulong? TotalActivityTime = null, - - [property: JsonPropertyName("override_score")] - decimal? OverrideScore = null, - - [property: JsonPropertyName("unposted_current_grade")] - string? UnpostedCurrentGrade = null, - - [property: JsonPropertyName("unposted_final_grade")] - string? UnpostedFinalGrade = null, - - [property: JsonPropertyName("unposted_current_score")] - string? UnpostedCurrentScore = null, - - [property: JsonPropertyName("unposted_final_score")] - string? UnpostedFinalScore = null, - - [property: JsonPropertyName("has_grading_periods")] - bool? HasGradingPeriods = null, - - [property: JsonPropertyName("totals_for_all_grading_periods_option")] - bool? TotalsForAllGradingPeriodsOption = null, - - [property: JsonPropertyName("current_grading_period_title")] - string? CurrentGradingPeriodTitle = null, - - [property: JsonPropertyName("current_grading_period_id")] - ulong? CurrentGradingPeriodId = null, - - [property: JsonPropertyName("current_period_override_grade")] - string? CurrentPeriodOverrideGrade = null, - - [property: JsonPropertyName("current_period_override_score")] - decimal? CurrentPeriodOverrideScore = null, - - [property: JsonPropertyName("current_period_unposted_final_score")] - decimal? CurrentPeriodUnpostedFinalScore = null, - - [property: JsonPropertyName("current_period_unposted_current_grade")] - string? CurrentPeriodUnpostedCurrentGrade = null, - - [property: JsonPropertyName("current_period_unposted_final_grade")] - string? CurrentPeriodUnpostedFinalGrade = null -); diff --git a/Management/Models/CanvasModels/Enrollments/GradeModel.cs b/Management/Models/CanvasModels/Enrollments/GradeModel.cs deleted file mode 100644 index cf770bb..0000000 --- a/Management/Models/CanvasModels/Enrollments/GradeModel.cs +++ /dev/null @@ -1,33 +0,0 @@ - - - -namespace CanvasModel.Enrollments; -public record GradeModel -( - [property: JsonPropertyName("html_url")] - string? HtmlUrl = null, - - [property: JsonPropertyName("current_grade")] - float? CurrentGrade = null, - - [property: JsonPropertyName("final_grade")] - float? FinalGrade = null, - - [property: JsonPropertyName("current_score")] - float? CurrentScore = null, - - [property: JsonPropertyName("final_score")] - float? FinalScore = null, - - [property: JsonPropertyName("unposted_current_grade")] - float? UnpostedCurrentGrade = null, - - [property: JsonPropertyName("unposted_final_grade")] - float? UnpostedFinalGrade = null, - - [property: JsonPropertyName("unposted_current_score")] - float? UnpostedCurrentScore = null, - - [property: JsonPropertyName("unposted_final_score")] - float? UnpostedFinalScore = null -); diff --git a/Management/Models/CanvasModels/Modules/CanvasModule.cs b/Management/Models/CanvasModels/Modules/CanvasModule.cs deleted file mode 100644 index 1eb5bcd..0000000 --- a/Management/Models/CanvasModels/Modules/CanvasModule.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace CanvasModel.Modules; - -public record CanvasModule( - [property: JsonPropertyName("id")] ulong Id, - [property: JsonPropertyName("workflow_state")] string WorkflowState, - [property: JsonPropertyName("position")] uint Position, - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("unlock_at")] DateTime? UnlockAt, - [property: JsonPropertyName("require_sequential_progress")] bool? RequireSequentialProgress, - [property: JsonPropertyName("prerequisite_module_ids")] IEnumerable? PrerequisiteModuleIds, - [property: JsonPropertyName("items_count")] uint ItemsCount, - [property: JsonPropertyName("items_url")] string ItemsUrl, - // [OptIn] - // [Enigmatic] // can be null if "the module is deemed too large", even if opted-in - [property: JsonPropertyName("items")] - IEnumerable? Items, - [property: JsonPropertyName("state")] string? State, // todo make sure this, - // [OptIn] - [property: JsonPropertyName("completed_at")] - DateTime? CompletedAt, - [property: JsonPropertyName("publish_final_grade")] bool? PublishFinalGrade, - [property: JsonPropertyName("published")] bool? Published -); diff --git a/Management/Models/CanvasModels/Modules/CanvasModuleCompletionRequirements.cs b/Management/Models/CanvasModels/Modules/CanvasModuleCompletionRequirements.cs deleted file mode 100644 index 8ede8b2..0000000 --- a/Management/Models/CanvasModels/Modules/CanvasModuleCompletionRequirements.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CanvasModel.Modules; - -public record CanvasCompletionRequirement( - [property: JsonPropertyName("type")] string Type, - [property: JsonPropertyName("min_score")] double? MinScore, - [property: JsonPropertyName("completed")] bool? Completed -); diff --git a/Management/Models/CanvasModels/Modules/CanvasModuleItem.cs b/Management/Models/CanvasModels/Modules/CanvasModuleItem.cs deleted file mode 100644 index f835c5f..0000000 --- a/Management/Models/CanvasModels/Modules/CanvasModuleItem.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace CanvasModel.Modules; - -public record CanvasModuleItem( - [property: JsonPropertyName("id")] ulong Id, - [property: JsonPropertyName("module_id")] ulong ModuleId, - [property: JsonPropertyName("position")] int Position, - [property: JsonPropertyName("title")] string Title, - [property: JsonPropertyName("indent")] uint? Indent, - [property: JsonPropertyName("type")] string Type, - [property: JsonPropertyName("content_id")] ulong? ContentId, - [property: JsonPropertyName("html_url")] string HtmlUrl, - [property: JsonPropertyName("url")] string? Url, - [property: JsonPropertyName("page_url")] string? PageUrl, - [property: JsonPropertyName("external_url")] string? ExternalUrl, - [property: JsonPropertyName("new_tab")] bool NewTab, - // [OptIn] - [property: JsonPropertyName("completion_requirement")] - CanvasCompletionRequirement? CompletionRequirement, - [property: JsonPropertyName("published")] bool? Published, - [property: JsonPropertyName("content_details")] CanvasModuleItemContentDetails? ContentDetails -); - -public record CanvasModuleItemContentDetails( - [property: JsonPropertyName("due_at")] DateTime? DueAt, - [property: JsonPropertyName("lock_at")] DateTime? LockAt, - [property: JsonPropertyName("points_possible")] double PointsPossible, - [property: JsonPropertyName("locked_for_user")] bool LockedForUser -); diff --git a/Management/Models/CanvasModels/Pages/CanvasPage.cs b/Management/Models/CanvasModels/Pages/CanvasPage.cs deleted file mode 100644 index 1185f2a..0000000 --- a/Management/Models/CanvasModels/Pages/CanvasPage.cs +++ /dev/null @@ -1,32 +0,0 @@ - -namespace CanvasModel.Pages; -public record CanvasPage( - [property: JsonPropertyName("page_id")] ulong PageId, - [property: JsonPropertyName("url")] string Url, - [property: JsonPropertyName("title")] string Title, - [property: JsonPropertyName("published")] bool Published, - [property: JsonPropertyName("front_page")] bool FrontPage, - [property: JsonPropertyName("body")] string? Body -); -// [JsonPropertyName("created_at")] -// public DateTime CreatedAt { get; set; } - -// [JsonPropertyName("updated_at")] -// public DateTime UpdatedAt { get; set; } - -// [JsonPropertyName("editing_roles")] -// public string EditingRoles { get; set; } - -// [JsonPropertyName("last_edited_by")] -// public UserDisplayModel LastEditedBy { get; set; } - -// [JsonPropertyName("locked_for_user")] -// public bool LockedForUser { get; set; } - -// [JsonPropertyName("lock_info")] -// public LockInfoModel? LockInfo { get; set; } - -// [JsonPropertyName("lock_explanation")] -// public string? LockExplanation { get; set; } - - diff --git a/Management/Models/CanvasModels/Quizzes/CanvasQuiz.cs b/Management/Models/CanvasModels/Quizzes/CanvasQuiz.cs deleted file mode 100644 index 2e74d9a..0000000 --- a/Management/Models/CanvasModels/Quizzes/CanvasQuiz.cs +++ /dev/null @@ -1,123 +0,0 @@ -using CanvasModel.Assignments; - -namespace CanvasModel.Quizzes; - -public record CanvasQuiz -{ - [JsonPropertyName("id")] - public ulong Id { get; init; } - - [JsonPropertyName("title")] - public required string Title { get; init; } - - [JsonPropertyName("html_url")] - public required string HtmlUrl { get; init; } - - [JsonPropertyName("mobile_url")] - public required string MobileUrl { get; init; } - - [JsonPropertyName("preview_url")] - public string? PreviewUrl { get; init; } - - [JsonPropertyName("description")] - public required string Description { get; init; } - - [JsonPropertyName("quiz_type")] - public required string QuizType { get; init; } - - [JsonPropertyName("assignment_group_id")] - public ulong? AssignmentGroupId { get; init; } - - [JsonPropertyName("time_limit")] - public decimal? TimeLimit { get; init; } - - [JsonPropertyName("shuffle_answers")] - public bool? ShuffleAnswers { get; init; } - - [JsonPropertyName("hide_results")] - public string? HideResults { get; init; } - - [JsonPropertyName("show_correct_answers")] - public bool? ShowCorrectAnswers { get; init; } - - [JsonPropertyName("show_correct_answers_last_attempt")] - public bool? ShowCorrectAnswersLastAttempt { get; init; } - - [JsonPropertyName("show_correct_answers_at")] - public DateTime? ShowCorrectAnswersAt { get; init; } - - [JsonPropertyName("hide_correct_answers_at")] - public DateTime? HideCorrectAnswersAt { get; init; } - - [JsonPropertyName("one_time_results")] - public bool? OneTimeResults { get; init; } - - [JsonPropertyName("scoring_policy")] - public string? ScoringPolicy { get; init; } - - [JsonPropertyName("allowed_attempts")] - public int AllowedAttempts { get; init; } - - [JsonPropertyName("one_question_at_a_time")] - public bool? OneQuestionAtATime { get; init; } - - [JsonPropertyName("question_count")] - public uint? QuestionCount { get; init; } - - [JsonPropertyName("points_possible")] - public decimal? PointsPossible { get; init; } - - [JsonPropertyName("cant_go_back")] - public bool? CantGoBack { get; init; } - - [JsonPropertyName("access_code")] - public string? AccessCode { get; init; } - - [JsonPropertyName("ip_filter")] - public string? IpFilter { get; init; } - - [JsonPropertyName("due_at")] - public DateTime? DueAt { get; init; } - - [JsonPropertyName("lock_at")] - public DateTime? LockAt { get; init; } - - [JsonPropertyName("unlock_at")] - public DateTime? UnlockAt { get; init; } - - [JsonPropertyName("published")] - public bool? Published { get; init; } - - [JsonPropertyName("unpublishable")] - public bool? Unpublishable { get; init; } - - [JsonPropertyName("locked_for_user")] - public bool? LockedForUser { get; init; } - - [JsonPropertyName("lock_info")] - public CanvasLockInfo? LockInfo { get; init; } - - [JsonPropertyName("lock_explanation")] - public string? LockExplanation { get; init; } - - [JsonPropertyName("speedgrader_url")] - public string? SpeedGraderUrl { get; init; } - - [JsonPropertyName("quiz_extensions_url")] - public string? QuizExtensionsUrl { get; init; } - - [JsonPropertyName("permissions")] - public required CanvasQuizPermissions Permissions { get; init; } - - [JsonPropertyName("all_dates")] - public object? AllDates { get; init; } - - [JsonPropertyName("version_number")] - public uint? VersionNumber { get; init; } - - [JsonPropertyName("question_types")] - public IEnumerable? QuestionTypes { get; init; } - - [JsonPropertyName("anonymous_submissions")] - public bool? AnonymousSubmissions { get; init; } -} diff --git a/Management/Models/CanvasModels/Quizzes/CanvasQuizAnswer.cs b/Management/Models/CanvasModels/Quizzes/CanvasQuizAnswer.cs deleted file mode 100644 index 0b3c247..0000000 --- a/Management/Models/CanvasModels/Quizzes/CanvasQuizAnswer.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace CanvasModel.Quizzes; - -public record CanvasQuizAnswer -{ - [JsonPropertyName("id")] - public ulong Id { get; init; } - - [JsonPropertyName("text")] - public required string Text { get; init; } - - [JsonPropertyName("html")] - public string? Html { get; init; } - - [JsonPropertyName("weight")] - public double Weight { get; init; } - - // [JsonPropertyName("comments")] - // public string? Comments { get; init; } - - // [JsonPropertyName("text_after_answers")] - // public string? TextAfterAnswers { get; init; } - - // [JsonPropertyName("answer_match_left")] - // public string? AnswerMatchLeft { get; init; } - - // [JsonPropertyName("answer_match_right")] - // public string? AnswerMatchRight { get; init; } - - // [JsonPropertyName("matching_answer_incorrect_matches")] - // public string? MatchingAnswerIncorrectMatches { get; init; } - - // [JsonPropertyName("numerical_answer_type")] - // public string? NumericalAnswerType { get; init; } - - // [JsonPropertyName("exact")] - // public int? Exact { get; init; } - - // [JsonPropertyName("margin")] - // public int? Margin { get; init; } - - // [JsonPropertyName("approximate")] - // public double? Approximate { get; init; } - - // [JsonPropertyName("precision")] - // public int? Precision { get; init; } - - // [JsonPropertyName("start")] - // public int? Start { get; init; } - - // [JsonPropertyName("end")] - // public int? End { get; init; } - - // [JsonPropertyName("blank_id")] - // public int? BlankId { get; init; } -} diff --git a/Management/Models/CanvasModels/Quizzes/CanvasQuizPermissions.cs b/Management/Models/CanvasModels/Quizzes/CanvasQuizPermissions.cs deleted file mode 100644 index 7a2c261..0000000 --- a/Management/Models/CanvasModels/Quizzes/CanvasQuizPermissions.cs +++ /dev/null @@ -1,27 +0,0 @@ - - -namespace CanvasModel.Quizzes; -public class CanvasQuizPermissions -{ - - [JsonPropertyName("read")] - public bool Read { get; set; } - - [JsonPropertyName("submit")] - public bool Submit { get; set; } - - [JsonPropertyName("create")] - public bool Create { get; set; } - - [JsonPropertyName("manage")] - public bool Manage { get; set; } - - [JsonPropertyName("read_statistics")] - public bool ReadStatistics { get; set; } - - [JsonPropertyName("review_grades")] - public bool ReviewGrades { get; set; } - - [JsonPropertyName("update")] - public bool Update { get; set; } -} diff --git a/Management/Models/CanvasModels/Quizzes/CanvasQuizQuestion.cs b/Management/Models/CanvasModels/Quizzes/CanvasQuizQuestion.cs deleted file mode 100644 index 1d4d64d..0000000 --- a/Management/Models/CanvasModels/Quizzes/CanvasQuizQuestion.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace CanvasModel.Quizzes; - -public record CanvasQuizQuestion -{ - [JsonPropertyName("id")] - public ulong Id { get; init; } - - [JsonPropertyName("quiz_id")] - public int QuizId { get; init; } - - [JsonPropertyName("position")] - public int? Position { get; init; } - - [JsonPropertyName("question_name")] - public required string QuestionName { get; init; } - - [JsonPropertyName("question_type")] - public required string QuestionType { get; init; } - - [JsonPropertyName("question_text")] - public required string QuestionText { get; init; } - - [JsonPropertyName("correct_comments")] - public required string CorrectComments { get; init; } - - [JsonPropertyName("incorrect_comments")] - public required string IncorrectComments { get; init; } - - [JsonPropertyName("neutral_comments")] - public required string NeutralComments { get; init; } - - [JsonPropertyName("answers")] - public IEnumerable? Answers { get; init; } -} diff --git a/Management/Models/CanvasModels/Submissions/MediaCommentModel.cs b/Management/Models/CanvasModels/Submissions/MediaCommentModel.cs deleted file mode 100644 index 3476939..0000000 --- a/Management/Models/CanvasModels/Submissions/MediaCommentModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace CanvasModel.Submissions; -public record MediaCommentModel -( - - [property: JsonPropertyName("content-type")] - string ContentType, - - [property: JsonPropertyName("display_name")] - string DisplayName, - - [property: JsonPropertyName("media_id")] - string MediaId, - - [property: JsonPropertyName("media_type")] - string MediaType, - - [property: JsonPropertyName("url")] - string Url -); diff --git a/Management/Models/CanvasModels/Submissions/SubmissionCommentModel.cs b/Management/Models/CanvasModels/Submissions/SubmissionCommentModel.cs deleted file mode 100644 index 06f90d3..0000000 --- a/Management/Models/CanvasModels/Submissions/SubmissionCommentModel.cs +++ /dev/null @@ -1,29 +0,0 @@ -using CanvasModel.Users; - -namespace CanvasModel.Submissions; -public record SubmissionCommentModel -( - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("author_id")] - ulong AuthorId, - - [property: JsonPropertyName("author_name")] - string AuthorName, - - [property: JsonPropertyName("author")] - UserDisplayModel Author, - - [property: JsonPropertyName("comment")] - string Comment, - - [property: JsonPropertyName("created_at")] - DateTime CreatedAt, - - [property: JsonPropertyName("edited_at")] - DateTime? EditedAt = null, - - [property: JsonPropertyName("media_comment")] - MediaCommentModel? MediaComment = null -); diff --git a/Management/Models/CanvasModels/Submissions/SubmissionModel.cs b/Management/Models/CanvasModels/Submissions/SubmissionModel.cs deleted file mode 100644 index 147119c..0000000 --- a/Management/Models/CanvasModels/Submissions/SubmissionModel.cs +++ /dev/null @@ -1,91 +0,0 @@ -using CanvasModel.Assignments; -using CanvasModel.Courses; -using CanvasModel.Users; - -namespace CanvasModel.Submissions; -public record SubmissionModel -( - [property: JsonPropertyName("assignment_id")] - ulong AssignmentId, - - [property: JsonPropertyName("grade")] - string Grade, - - [property: JsonPropertyName("html_url")] - string HtmlUrl, - - [property: JsonPropertyName("preview_url")] - string PreviewUrl, - - [property: JsonPropertyName("submission_type")] - string SubmissionType, - - [property: JsonPropertyName("user_id")] - ulong UserId, - - [property: JsonPropertyName("user")] - UserModel User, - - [property: JsonPropertyName("workflow_state")] - string WorkflowState, - - [property: JsonPropertyName("late_policy_status")] - string LatePolicyStatus, - - [property: JsonPropertyName("assignment")] - CanvasAssignment? Assignment = null, - - [property: JsonPropertyName("course")] - CourseModel? Course = null, - - [property: JsonPropertyName("attempt")] - uint? Attempt = null, - - [property: JsonPropertyName("body")] - string? Body = null, - - [property: JsonPropertyName("grade_matches_current_submission")] - bool? GradeMatchesCurrentSubmission = null, - - [property: JsonPropertyName("score")] - decimal? Score = null, - - [property: JsonPropertyName("submission_comments")] - IEnumerable? SubmissionComments = null, - - [property: JsonPropertyName("submitted_at")] - DateTime? SubmittedAt = null, - - [property: JsonPropertyName("url")] - string? Url = null, - - [property: JsonPropertyName("grader_id")] - long? GraderId = null, - - [property: JsonPropertyName("graded_at")] - DateTime? GradedAt = null, - - [property: JsonPropertyName("late")] - bool? Late = null, - - [property: JsonPropertyName("assignment_visible")] - bool? AssignmentVisible = null, - - [property: JsonPropertyName("excused")] - bool? Excused = null, - - [property: JsonPropertyName("missing")] - bool? Missing = null, - - [property: JsonPropertyName("points_deducted")] - double? PointsDeducted = null, - - [property: JsonPropertyName("seconds_late")] - double? SecondsLate = null, - - [property: JsonPropertyName("extra_attempts")] - uint? ExtraAttempts = null, - - [property: JsonPropertyName("anonymous_id")] - string? AnonymousId = null -); diff --git a/Management/Models/CanvasModels/Users/ActivityStreamObjectModel.cs b/Management/Models/CanvasModels/Users/ActivityStreamObjectModel.cs deleted file mode 100644 index 5e0218b..0000000 --- a/Management/Models/CanvasModels/Users/ActivityStreamObjectModel.cs +++ /dev/null @@ -1,163 +0,0 @@ -using CanvasModel.Assignments; -using CanvasModel.Courses; -using CanvasModel.Submissions; - -namespace CanvasModel.Users; -public record ActivityStreamObjectModel -( - [property: JsonPropertyName("created_at")] - DateTime CreatedAt, - - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("title")] - string Title, - - [property: JsonPropertyName("message")] - string Message, - - [property: JsonPropertyName("type")] - string Type, - - [property: JsonPropertyName("read_state")] - bool ReadState, - - [property: JsonPropertyName("context_type")] - string ContextType, - - [property: JsonPropertyName("html_url")] - string HtmlUrl, - - [property: JsonPropertyName("notification_category")] - string NotificationCategory, - - [property: JsonPropertyName("grade")] - string Grade, - - [property: JsonPropertyName("preview_url")] - string PreviewUrl, - - [property: JsonPropertyName("submission_type")] - string SubmissionType, - - [property: JsonPropertyName("late_policy_status")] - string LatePolicyStatus, - - [property: JsonPropertyName("workflow_state")] - string WorkflowState, - - [property: JsonPropertyName("updated_at")] - DateTime? UpdatedAt = null, - - [property: JsonPropertyName("course_id")] - ulong? CourseId = null, - - [property: JsonPropertyName("group_id")] - ulong? GroupId = null, - - [property: JsonPropertyName("total_root_discussion_entries")] - uint? TotalRootDiscussionEntries = null, - - [property: JsonPropertyName("require_initial_post")] - bool? RequireInitialPost = null, - - [property: JsonPropertyName("user_has_posted")] - bool? UserHasPosted = null, - - [property: JsonPropertyName("root_discussion_entries")] - object? RootDiscussionEntries = null, - - [property: JsonPropertyName("discussion_topic_id")] - ulong? DiscussionTopicId = null, - - [property: JsonPropertyName("announcement_id")] - ulong? AnnouncementId = null, - - [property: JsonPropertyName("conversation_id")] - ulong? ConversationId = null, - - [property: JsonPropertyName("private")] - bool? Private = null, - - [property: JsonPropertyName("participant_count")] - uint? ParticipantCount = null, - - [property: JsonPropertyName("message_id")] - ulong? MessageId = null, - - [property: JsonPropertyName("assignment_id")] - ulong? AssignmentId = null, - - [property: JsonPropertyName("assignment")] - CanvasAssignment? Assignment = null, - - [property: JsonPropertyName("course")] - CourseModel? Course = null, - - [property: JsonPropertyName("attempt")] - uint? Attempt = null, - - [property: JsonPropertyName("body")] - string? Body = null, - - [property: JsonPropertyName("grade_matches_current_submission")] - bool? GradeMatchesCurrentSubmission = null, - - [property: JsonPropertyName("score")] - decimal? Score = null, - - [property: JsonPropertyName("submission_comments")] - IEnumerable? SubmissionComments = null, - - [property: JsonPropertyName("submitted_at")] - DateTime? SubmittedAt = null, - - [property: JsonPropertyName("url")] - string? Url = null, - - [property: JsonPropertyName("user_id")] - ulong? UserId = null, - - [property: JsonPropertyName("grader_id")] - long? GraderId = null, - - [property: JsonPropertyName("graded_at")] - DateTime? GradedAt = null, - - [property: JsonPropertyName("user")] - UserModel? User = null, - - [property: JsonPropertyName("late")] - bool? Late = null, - - [property: JsonPropertyName("assignment_visible")] - bool? AssignmentVisible = null, - - [property: JsonPropertyName("excused")] - bool? Excused = null, - - [property: JsonPropertyName("missing")] - bool? Missing = null, - - [property: JsonPropertyName("points_deducted")] - double? PointsDeducted = null, - - [property: JsonPropertyName("seconds_late")] - double? SecondsLate = null, - - [property: JsonPropertyName("extra_attempts")] - uint? ExtraAttempts = null, - - [property: JsonPropertyName("anonymous_id")] - string? AnonymousId = null, - - [property: JsonPropertyName("web_conference_id")] - ulong? WebConferenceId = null, - - [property: JsonPropertyName("collaboration_id")] - ulong? CollaborationId = null, - - [property: JsonPropertyName("assignment_request_id")] - ulong? AssignmentRequestId = null -); diff --git a/Management/Models/CanvasModels/Users/ActivityStreamSummaryEntryModel.cs b/Management/Models/CanvasModels/Users/ActivityStreamSummaryEntryModel.cs deleted file mode 100644 index bf387a7..0000000 --- a/Management/Models/CanvasModels/Users/ActivityStreamSummaryEntryModel.cs +++ /dev/null @@ -1,15 +0,0 @@ - - - -namespace CanvasModel.Users; -public record ActivityStreamSummaryEntryModel -( - [property: JsonPropertyName("type")] - string Type, - - [property: JsonPropertyName("unread_count")] - uint UnreadCount, - - [property: JsonPropertyName("count")] - uint Count -); diff --git a/Management/Models/CanvasModels/Users/AnonymousUserDisplayModel.cs b/Management/Models/CanvasModels/Users/AnonymousUserDisplayModel.cs deleted file mode 100644 index e1727f8..0000000 --- a/Management/Models/CanvasModels/Users/AnonymousUserDisplayModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CanvasModel.Users; - -public record AnonymousUserDisplayModel -( - [property: JsonPropertyName("anonymous_id")] - string AnonymousId, - - [property: JsonPropertyName("avatar_image_url")] - string AvatarImageUrl -); diff --git a/Management/Models/CanvasModels/Users/AvatarModel.cs b/Management/Models/CanvasModels/Users/AvatarModel.cs deleted file mode 100644 index 1106d0a..0000000 --- a/Management/Models/CanvasModels/Users/AvatarModel.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace CanvasModel.Users; - -public record AvatarModel -( - [property: JsonPropertyName("type")] - string Type, - - [property: JsonPropertyName("url")] - string Url, - - [property: JsonPropertyName("token")] - string Token, - - [property: JsonPropertyName("display_name")] - string DisplayName, - - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("content_type")] - string ContentType, - - [property: JsonPropertyName("filename")] - string Filename, - - [property: JsonPropertyName("size")] - ulong Size -); diff --git a/Management/Models/CanvasModels/Users/CourseNicknameModel.cs b/Management/Models/CanvasModels/Users/CourseNicknameModel.cs deleted file mode 100644 index e7f78e0..0000000 --- a/Management/Models/CanvasModels/Users/CourseNicknameModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace CanvasModel.Users; - -public record CourseNicknameModel -( - [property: JsonPropertyName("course_id")] - ulong CourseId, - - [property: JsonPropertyName("name")] - string Name, - - [property: JsonPropertyName("nickname")] - string Nickname -); diff --git a/Management/Models/CanvasModels/Users/PageViewLinksModel.cs b/Management/Models/CanvasModels/Users/PageViewLinksModel.cs deleted file mode 100644 index 3d22764..0000000 --- a/Management/Models/CanvasModels/Users/PageViewLinksModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace CanvasModel.Users; - -public record PageViewLinksModel -( - [property: JsonPropertyName("user")] - ulong User, - - [property: JsonPropertyName("context")] - ulong? Context = null, - - [property: JsonPropertyName("asset")] - ulong? Asset = null, - - [property: JsonPropertyName("real_user")] - ulong? RealUser = null, - - [property: JsonPropertyName("account")] - ulong? Account = null -); diff --git a/Management/Models/CanvasModels/Users/PageViewModel.cs b/Management/Models/CanvasModels/Users/PageViewModel.cs deleted file mode 100644 index 62f69ef..0000000 --- a/Management/Models/CanvasModels/Users/PageViewModel.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace CanvasModel.Users; - -public record PageViewModel -( - [property: JsonPropertyName("id")] - string Id, - - [property: JsonPropertyName("app_name")] - string AppName, - - [property: JsonPropertyName("url")] - string Url, - - [property: JsonPropertyName("context_type")] - string ContextType, - - [property: JsonPropertyName("asset_type")] - string AssetType, - - [property: JsonPropertyName("controller")] - string Controller, - - [property: JsonPropertyName("action")] - string Action, - - [property: JsonPropertyName("created_at")] - DateTime CreatedAt, - - [property: JsonPropertyName("links")] - PageViewLinksModel Links, - - [property: JsonPropertyName("user_agent")] - string UserAgent, - - [property: JsonPropertyName("http_method")] - string HttpMethod, - - [property: JsonPropertyName("remote_ip")] - string RemoteIp, - - [property: JsonPropertyName("interaction_seconds")] - decimal? InteractionSeconds = null, - - [property: JsonPropertyName("user_request")] - bool? UserRequest = null, - - [property: JsonPropertyName("render_time")] - double? RenderTime = null, - - - [property: JsonPropertyName("participated")] - bool? Participated = null -); diff --git a/Management/Models/CanvasModels/Users/ProfileModel.cs b/Management/Models/CanvasModels/Users/ProfileModel.cs deleted file mode 100644 index bef7721..0000000 --- a/Management/Models/CanvasModels/Users/ProfileModel.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace CanvasModel.Users; - -public record ProfileModel -( - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("name")] - string Name, - - [property: JsonPropertyName("short_name")] - string ShortName, - - [property: JsonPropertyName("sortable_name")] - string SortableName, - - [property: JsonPropertyName("title")] - string Title, - - [property: JsonPropertyName("bio")] - string Bio, - - [property: JsonPropertyName("primary_email")] - string PrimaryEmail, - - [property: JsonPropertyName("login_id")] - string LoginId, - - [property: JsonPropertyName("sis_user_id")] - string SisUserId, - - [property: JsonPropertyName("lti_user_id")] - string LtiUserId, - - [property: JsonPropertyName("avatar_url")] - string AvatarUrl, - - [property: JsonPropertyName("calendar")] - object Calendar, - - [property: JsonPropertyName("time_zone")] - string TimeZone, - - [property: JsonPropertyName("locale")] - string Locale -); diff --git a/Management/Models/CanvasModels/Users/ShortUserModel.cs b/Management/Models/CanvasModels/Users/ShortUserModel.cs deleted file mode 100644 index 3d9cd79..0000000 --- a/Management/Models/CanvasModels/Users/ShortUserModel.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace CanvasModel.Users; - -public record ShortUserModel -( - - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("display_name")] - string DisplayName, - - [property: JsonPropertyName("avatar_image_url")] - string AvatarImageUrl, - - [property: JsonPropertyName("html_url")] - string HtmlUrl -); diff --git a/Management/Models/CanvasModels/Users/UserDisplayModel.cs b/Management/Models/CanvasModels/Users/UserDisplayModel.cs deleted file mode 100644 index e4bc783..0000000 --- a/Management/Models/CanvasModels/Users/UserDisplayModel.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace CanvasModel.Users; - -public record UserDisplayModel -( - [property: JsonPropertyName("avatar_image_url")] - string AvatarImageUrl, - - [property: JsonPropertyName("html_url")] - string HtmlUrl, - - [property: JsonPropertyName("anonymous_id")] - string AnonymousId, - - [property: JsonPropertyName("id")] - ulong? Id = null, - - [property: JsonPropertyName("short_name")] - string? ShortName = null, - - [property: JsonPropertyName("display_name")] - string? DisplayName = null, - - [property: JsonPropertyName("pronouns")] - string? Pronouns = null -); diff --git a/Management/Models/CanvasModels/Users/UserModel.cs b/Management/Models/CanvasModels/Users/UserModel.cs deleted file mode 100644 index 0139727..0000000 --- a/Management/Models/CanvasModels/Users/UserModel.cs +++ /dev/null @@ -1,56 +0,0 @@ -using CanvasModel.Enrollments; - -namespace CanvasModel.Users; -public record UserModel -( - [property: JsonPropertyName("id")] - ulong Id, - - [property: JsonPropertyName("name")] - string Name, - - [property: JsonPropertyName("sortable_name")] - string SortableName, - - [property: JsonPropertyName("short_name")] - string ShortName, - - [property: JsonPropertyName("sis_user_id")] - string SisUserId, - - [property: JsonPropertyName("integration_id")] - string IntegrationId, - - [property: JsonPropertyName("login_id")] - string LoginId, - - [property: JsonPropertyName("avatar_url")] - string AvatarUrl, - - [property: JsonPropertyName("enrollments")] - List Enrollments, - - [property: JsonPropertyName("email")] - string Email, - - [property: JsonPropertyName("locale")] - string Locale, - - [property: JsonPropertyName("effective_locale")] - string EffectiveLocale, - - [property: JsonPropertyName("time_zone")] - string TimeZone, - - [property: JsonPropertyName("bio")] - string Bio, - - [property: JsonPropertyName("permissions")] - Dictionary Permissions, - - [property: JsonPropertyName("sis_import_id")] - ulong? SisImportId = null, - - [property: JsonPropertyName("last_login")] - DateTime? LastLogin = null -); diff --git a/Management/Models/Local/Assignment/AssignemntParseExceptions.cs b/Management/Models/Local/Assignment/AssignemntParseExceptions.cs deleted file mode 100644 index 08402bf..0000000 --- a/Management/Models/Local/Assignment/AssignemntParseExceptions.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace LocalModels; -public class RubricMarkdownParseException : Exception -{ - public RubricMarkdownParseException(string message) : base(message) { } -} -public class AssignmentMarkdownParseException : Exception -{ - public AssignmentMarkdownParseException(string message) : base(message) { } -} diff --git a/Management/Models/Local/Assignment/AssignmentSubmissionType.cs b/Management/Models/Local/Assignment/AssignmentSubmissionType.cs deleted file mode 100644 index c9170dd..0000000 --- a/Management/Models/Local/Assignment/AssignmentSubmissionType.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace LocalModels; - -public static class AssignmentSubmissionType -{ - public static readonly string ONLINE_TEXT_ENTRY = "online_text_entry"; - public static readonly string ONLINE_UPLOAD = "online_upload"; - public static readonly string ONLINE_QUIZ = "online_quiz"; - // public static readonly string ON_PAPER = "on_paper"; - public static readonly string DISCUSSION_TOPIC = "discussion_topic"; - // public static readonly string EXTERNAL_TOOL = "external_tool"; - public static readonly string ONLINE_URL = "online_url"; - // public static readonly string MEDIA_RECORDING = "media_recording"; - // public static readonly string STUDENT_ANNOTATION = "student_annotation"; - public static readonly string NONE = "none"; - public static readonly IEnumerable AllTypes = new string[] - { - ONLINE_TEXT_ENTRY, - ONLINE_UPLOAD, - ONLINE_QUIZ, - // ON_PAPER, - DISCUSSION_TOPIC, - // EXTERNAL_TOOL, - ONLINE_URL, - // MEDIA_RECORDING, - // STUDENT_ANNOTATION, - NONE, - }; -} diff --git a/Management/Models/Local/Assignment/AssignmentTemplate.cs b/Management/Models/Local/Assignment/AssignmentTemplate.cs deleted file mode 100644 index 0c2930b..0000000 --- a/Management/Models/Local/Assignment/AssignmentTemplate.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Text.RegularExpressions; - -namespace LocalModels; - -public record AssignmentTemplate -{ - public string Id { get; set; } = String.Empty; - public string Name { get; set; } = String.Empty; - public string Markdown { get; set; } = String.Empty; - - public static IEnumerable GetVariables(string markdown) - { - string pattern = "{{(.*?)}}"; - MatchCollection matches = Regex.Matches(markdown, pattern); - - return matches.Select(match => match.Groups[1].Value); - } - // public static string GetHtml(AssignmentTemplate template, LocalAssignment assignment) - // { - - // var html = MarkdownService.Render(template.Markdown); - - // foreach (KeyValuePair entry in assignment.template_variables) - // { - // html = html.Replace($"%7B%7B{entry.Key}%7D%7D", entry.Value); - // } - // return html; - // } - -} diff --git a/Management/Models/Local/Assignment/LocalAssignment.cs b/Management/Models/Local/Assignment/LocalAssignment.cs deleted file mode 100644 index a31eddf..0000000 --- a/Management/Models/Local/Assignment/LocalAssignment.cs +++ /dev/null @@ -1,50 +0,0 @@ - -namespace LocalModels; - -public sealed record LocalAssignment : IModuleItem -{ - private string _name = ""; - public string Name - { - get => _name; - init - { - if (value.Contains(':')) - throw new AssignmentMarkdownParseException("Name cannot contain a ':' character, it breaks windows filesystems " + value); - _name = value; - } - } - public string Description { get; init; } = ""; - public DateTime? LockAt { get; init; } - public DateTime DueAt { get; init; } - public string? LocalAssignmentGroupName { get; init; } - public IEnumerable SubmissionTypes { get; init; } = Array.Empty(); - public IEnumerable AllowedFileUploadExtensions { get; init; } = Array.Empty(); - public IEnumerable Rubric { get; init; } = Array.Empty(); - public double PointsPossible => Rubric.Sum(r => r.IsExtraCredit ? 0 : r.Points); - - - public string GetDescriptionHtml() - { - return MarkdownService.Render(Description); - } - - public ulong? GetCanvasAssignmentGroupId(IEnumerable assignmentGroups) => - assignmentGroups - .FirstOrDefault(g => g.Name == LocalAssignmentGroupName)? - .CanvasId; - - public string ToMarkdown() => this.AssignmentToMarkdown(); - public string RubricToMarkdown() => this.AssignmentRubricToMarkdown(); - public static LocalAssignment ParseMarkdown(string input) => LocalAssignmentMarkdownParser.ParseMarkdown(input); - public static IEnumerable ParseRubricMarkdown(string rawMarkdown) => LocalAssignmentMarkdownParser.ParseRubricMarkdown(rawMarkdown); - - - public bool Equals(LocalAssignment? otherAssignment) - { - return ToMarkdown() == otherAssignment?.ToMarkdown(); - } - - public override int GetHashCode() => ToMarkdown().GetHashCode(); - -} diff --git a/Management/Models/Local/Assignment/LocalAssignmentGroup.cs b/Management/Models/Local/Assignment/LocalAssignmentGroup.cs deleted file mode 100644 index 44253f6..0000000 --- a/Management/Models/Local/Assignment/LocalAssignmentGroup.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace LocalModels; - -public record LocalAssignmentGroup -{ - public ulong? CanvasId { get; init; } - public string Id { get; init; } = string.Empty; - public required string Name { get; init; } - public double Weight { get; init; } -} diff --git a/Management/Models/Local/Assignment/LocalAssignmentMarkdownCreator.cs b/Management/Models/Local/Assignment/LocalAssignmentMarkdownCreator.cs deleted file mode 100644 index 14e3ec5..0000000 --- a/Management/Models/Local/Assignment/LocalAssignmentMarkdownCreator.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Text; -using LocalModels; - -namespace LocalModels; -public static class LocalAssignmentMarkdownCreator -{ - public static string AssignmentToMarkdown(this LocalAssignment assignment) - { - var settingsMarkdown = assignment.settingsToMarkdown(); - var rubricMarkdown = assignment.RubricToMarkdown(); - var assignmentMarkdown = - settingsMarkdown + "\n" - + "---\n\n" - + assignment.Description + "\n\n" - + "## Rubric\n\n" - + rubricMarkdown; - - return assignmentMarkdown; - } - - public static string AssignmentRubricToMarkdown(this LocalAssignment assignment) - { - var builder = new StringBuilder(); - foreach (var item in assignment.Rubric) - { - var pointLabel = item.Points > 1 ? "pts" : "pt"; - builder.Append($"- {item.Points}{pointLabel}: {item.Label}" + "\n"); - } - return builder.ToString(); - } - - private static string settingsToMarkdown(this LocalAssignment assignment) - { - var printableDueDate = assignment.DueAt.ToString().Replace('\u202F', ' '); - var printableLockAt = assignment.LockAt?.ToString().Replace('\u202F', ' ') ?? ""; - var builder = new StringBuilder(); - builder.Append($"Name: {assignment.Name}" + "\n"); - builder.Append($"LockAt: {printableLockAt}" + "\n"); - builder.Append($"DueAt: {printableDueDate}" + "\n"); - builder.Append($"AssignmentGroupName: {assignment.LocalAssignmentGroupName}" + "\n"); - builder.Append($"SubmissionTypes:" + "\n"); - foreach (var submissionType in assignment.SubmissionTypes) - { - builder.Append($"- {submissionType}" + "\n"); - } - builder.Append($"AllowedFileUploadExtensions:" + "\n"); - foreach (var fileExtension in assignment.AllowedFileUploadExtensions) - { - builder.Append($"- {fileExtension}" + "\n"); - } - return builder.ToString(); - } -} diff --git a/Management/Models/Local/Assignment/LocalAssignmentMarkdownParser.cs b/Management/Models/Local/Assignment/LocalAssignmentMarkdownParser.cs deleted file mode 100644 index adb327b..0000000 --- a/Management/Models/Local/Assignment/LocalAssignmentMarkdownParser.cs +++ /dev/null @@ -1,141 +0,0 @@ - - -using System.Text.RegularExpressions; - -namespace LocalModels; -public static class LocalAssignmentMarkdownParser -{ - - public static LocalAssignment ParseMarkdown(string input) - { - var settingsString = input.Split("---")[0]; - var (name, localAssignmentGroupName, submissionTypes, fileUploadExtensions, dueAt, lockAt) = parseSettings(settingsString); - - var description = String.Join("---\n", input.Split("---\n")[1..]).Split("## Rubric")[0]; - - var rubricString = input.Split("## Rubric\n")[1]; - var rubric = ParseRubricMarkdown(rubricString); - return new LocalAssignment() - { - Name = name.Trim(), - LocalAssignmentGroupName = localAssignmentGroupName.Trim(), - SubmissionTypes = submissionTypes, - AllowedFileUploadExtensions = fileUploadExtensions, - DueAt = dueAt, - LockAt = lockAt, - Rubric = rubric, - Description = description.Trim() - }; - } - - private static (string name, string assignmentGroupName, List submissionTypes, List fileUploadExtensions, DateTime dueAt, DateTime? lockAt) parseSettings(string input) - { - var name = MarkdownUtils.ExtractLabelValue(input, "Name"); - var rawLockAt = MarkdownUtils.ExtractLabelValue(input, "LockAt"); - var rawDueAt = MarkdownUtils.ExtractLabelValue(input, "DueAt"); - var localAssignmentGroupName = MarkdownUtils.ExtractLabelValue(input, "AssignmentGroupName"); - var submissionTypes = parseSubmissionTypes(input); - var fileUploadExtensions = parseFileUploadExtensions(input); - - DateTime? lockAt = DateTime.TryParse(rawLockAt, out DateTime parsedLockAt) - ? parsedLockAt - : null; - var dueAt = DateTime.TryParse(rawDueAt, out DateTime parsedDueAt) - ? parsedDueAt - : throw new AssignmentMarkdownParseException($"Error with DueAt: {rawDueAt}"); - - return (name, localAssignmentGroupName, submissionTypes, fileUploadExtensions, dueAt, lockAt); - - - } - - private static List parseSubmissionTypes(string input) - { - input = input.Replace("\r\n", "\n"); - List submissionTypes = []; - - // Define a regular expression pattern to match the bulleted list items - string startOfTypePattern = @"- (.+)"; - Regex regex = new(startOfTypePattern); - - var words = input.Split("SubmissionTypes:"); - var inputAfterSubmissionTypes = words[1]; - - string[] lines = inputAfterSubmissionTypes.Split("\n", StringSplitOptions.RemoveEmptyEntries); - - foreach (string line in lines) - { - string trimmedLine = line.Trim(); - Match match = regex.Match(trimmedLine); - - if (!match.Success) - break; - - string type = match.Groups[1].Value.Trim(); - submissionTypes.Add(type); - } - - return submissionTypes; - } - - private static List parseFileUploadExtensions(string input) - { - input = input.Replace("\r\n", "\n"); - List allowedFileUploadExtensions = []; - - // Define a regular expression pattern to match the bulleted list items - string startOfTypePattern = @"- (.+)"; - Regex regex = new(startOfTypePattern); - - var words = input.Split("AllowedFileUploadExtensions:"); - if(words.Length < 2) - return []; - var inputAfterSubmissionTypes = words[1]; - - string[] lines = inputAfterSubmissionTypes.Split("\n", StringSplitOptions.RemoveEmptyEntries); - - foreach (string line in lines) - { - string trimmedLine = line.Trim(); - Match match = regex.Match(trimmedLine); - - if (!match.Success) - break; - - string type = match.Groups[1].Value.Trim(); - allowedFileUploadExtensions.Add(type); - } - - return allowedFileUploadExtensions; - } - - - - public static IEnumerable ParseRubricMarkdown(string rawMarkdown) - { - if (rawMarkdown.Trim() == string.Empty) - return []; - var lines = rawMarkdown.Trim().Split("\n"); - var items = lines.Select(parseIndividualRubricItemMarkdown).ToArray(); - return items; - } - - private static RubricItem parseIndividualRubricItemMarkdown(string rawMarkdown) - { - var pointsPattern = @"\s*-\s*(-?\d+(?:\.\d+)?)\s*pt(s)?:"; - var match = Regex.Match(rawMarkdown, pointsPattern); - if (!match.Success) - throw new RubricMarkdownParseException($"points not found: {rawMarkdown}"); - - var points = double.Parse(match.Groups[1].Value); - - var label = string.Join(": ", rawMarkdown.Split(": ").Skip(1)); - - return new RubricItem() - { - Points = points, - Label = label - }; - } - -} diff --git a/Management/Models/Local/Assignment/RubricItem.cs b/Management/Models/Local/Assignment/RubricItem.cs deleted file mode 100644 index 8bf2c52..0000000 --- a/Management/Models/Local/Assignment/RubricItem.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace LocalModels; - -public record RubricItem -{ - public static readonly string extraCredit = "(Extra Credit) "; - public required string Label { get; set; } - public required double Points { get; set; } - public bool IsExtraCredit => Label.Contains(extraCredit.ToLower(), StringComparison.CurrentCultureIgnoreCase); -} diff --git a/Management/Models/Local/IModuleItem.cs b/Management/Models/Local/IModuleItem.cs deleted file mode 100644 index 269e4bd..0000000 --- a/Management/Models/Local/IModuleItem.cs +++ /dev/null @@ -1,9 +0,0 @@ - -namespace LocalModels; - -public interface IModuleItem -{ - public string Name { get; init; } - public DateTime DueAt { get; init; } - -} diff --git a/Management/Models/Local/LocalCourse.cs b/Management/Models/Local/LocalCourse.cs deleted file mode 100644 index f4ad997..0000000 --- a/Management/Models/Local/LocalCourse.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace LocalModels; - -public record LocalCourse -{ - public IEnumerable Modules { get; init; } = Enumerable.Empty(); - public required LocalCourseSettings Settings { get; init; } -} - -public record SimpleTimeOnly -{ - public int Hour { get; init; } = 1; - public int Minute { get; init; } = 0; -} diff --git a/Management/Models/Local/LocalCoursePage.cs b/Management/Models/Local/LocalCoursePage.cs deleted file mode 100644 index 9e5e4d7..0000000 --- a/Management/Models/Local/LocalCoursePage.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace LocalModels; - -public record LocalCoursePage : IModuleItem -{ - public required string Name { get; init; } - public required string Text { get; set; } - public DateTime DueAt { get; init; } - public string GetBodyHtml() => MarkdownService.Render(Text); - - public string ToMarkdown() - { - var printableDueDate = DueAt.ToString()?.Replace('\u202F', ' '); - var settingsMarkdown = $"Name: {Name}\n" - + $"DueDateForOrdering: {printableDueDate}\n" - + "---\n"; - return settingsMarkdown + Text; - } - public static LocalCoursePage ParseMarkdown(string pageMarkdown) - { - var rawSettings = pageMarkdown.Split("---")[0]; - var name = MarkdownUtils.ExtractLabelValue(rawSettings, "Name"); - var rawDate = MarkdownUtils.ExtractLabelValue(rawSettings, "DueDateForOrdering"); - - DateTime parsedDate = DateTime.TryParse(rawDate, out DateTime parsedDueAt) - ? parsedDueAt - : throw new LocalPageMarkdownParseException($"could not parse due date: {rawDate}"); - - - var text = pageMarkdown.Split("---\n")[1]; - - return new LocalCoursePage - { - Name = name, - DueAt = parsedDate, - Text = text - }; - } - -} - - -public class LocalPageMarkdownParseException : Exception -{ - public LocalPageMarkdownParseException(string message) : base(message) - { - - } -} diff --git a/Management/Models/Local/LocalCourseSettings.cs b/Management/Models/Local/LocalCourseSettings.cs deleted file mode 100644 index 9790605..0000000 --- a/Management/Models/Local/LocalCourseSettings.cs +++ /dev/null @@ -1,34 +0,0 @@ -using YamlDotNet.Serialization; - -namespace LocalModels; - -public record LocalCourseSettings -{ - public IEnumerable AssignmentGroups { get; init; } = - Enumerable.Empty(); - - [YamlIgnore] - public string Name { get; init; } = string.Empty; - public IEnumerable DaysOfWeek { get; init; } = Enumerable.Empty(); - public ulong? CanvasId { get; init; } - public DateTime StartDate { get; init; } - public DateTime EndDate { get; init; } - public SimpleTimeOnly DefaultDueTime { get; init; } = new SimpleTimeOnly(); - - public string ToYaml() - { - var serializer = new SerializerBuilder().DisableAliases().Build(); - var yaml = serializer.Serialize(this); - return yaml; - } - - public static LocalCourseSettings ParseYaml(string rawText) - { - var deserializer = new DeserializerBuilder() - .IgnoreUnmatchedProperties() - .Build(); - - var settings = deserializer.Deserialize(rawText); - return settings; - } -} diff --git a/Management/Models/Local/LocalModules.cs b/Management/Models/Local/LocalModules.cs deleted file mode 100644 index c0b0f23..0000000 --- a/Management/Models/Local/LocalModules.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace LocalModels; - -public sealed record LocalModule -{ - public string Name { get; init; } = string.Empty; - public string Notes { get; set; } = string.Empty; - public IEnumerable Assignments { get; init; } = []; - public IEnumerable Quizzes { get; init; } = []; - public IEnumerable Pages { get; init; } = []; - - public IEnumerable GetSortedModuleItems() => - Enumerable.Empty() - .Concat(Assignments) - .Concat(Quizzes) - .Concat(Pages) - .OrderBy(i => i.DueAt); - - public bool Equals(LocalModule? otherModule) - { - var areEqual = - string.Equals(Name, otherModule?.Name, StringComparison.OrdinalIgnoreCase) - && string.Equals(Notes, otherModule?.Notes, StringComparison.OrdinalIgnoreCase) - && CompareCollections(Assignments.OrderBy(x => x.Name), otherModule?.Assignments.OrderBy(x => x.Name)) - && CompareCollections(Quizzes.OrderBy(x => x.Name), otherModule?.Quizzes.OrderBy(x => x.Name)) - && CompareCollections(Pages.OrderBy(x => x.Name), otherModule?.Pages.OrderBy(x => x.Name)); - return areEqual; - } - - private static bool CompareCollections(IEnumerable first, IEnumerable? second) - { - var firstList = first.ToList(); - var secondList = second?.ToList(); - - if (firstList.Count != secondList?.Count) - return false; - - for (int i = 0; i < firstList.Count; i++) - { - if (!Equals(firstList[i], secondList[i])) - return false; - } - - return true; - } - - public override int GetHashCode() - { - HashCode hash = new HashCode(); - hash.Add(Name, StringComparer.OrdinalIgnoreCase); - hash.Add(Notes, StringComparer.OrdinalIgnoreCase); - AddRangeToHash(hash, Assignments.OrderBy(x => x.Name)); - AddRangeToHash(hash, Quizzes.OrderBy(x => x.Name)); - AddRangeToHash(hash, Pages.OrderBy(x => x.Name)); - return hash.ToHashCode(); - } - - private void AddRangeToHash(HashCode hash, IEnumerable items) - { - foreach (var item in items) - { - hash.Add(item); - } - } -} diff --git a/Management/Models/Local/MarkdownUtils.cs b/Management/Models/Local/MarkdownUtils.cs deleted file mode 100644 index 8849265..0000000 --- a/Management/Models/Local/MarkdownUtils.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Text.RegularExpressions; - -namespace LocalModels; - -public static class MarkdownUtils -{ - public static string ExtractLabelValue(string input, string label) - { - string pattern = $@"{label}: (.*?)\n"; - Match match = Regex.Match(input, pattern); - - if (match.Success) - { - return match.Groups[1].Value; - } - - return string.Empty; - } - -} diff --git a/Management/Models/Local/Quiz/LocalQuiz.cs b/Management/Models/Local/Quiz/LocalQuiz.cs deleted file mode 100644 index bde6d4b..0000000 --- a/Management/Models/Local/Quiz/LocalQuiz.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System.Text.RegularExpressions; -using YamlDotNet.Serialization; - -namespace LocalModels; - -public record LocalQuiz : IModuleItem -{ - - public required string Name { get; init; } - public required string Description { get; init; } - public string? Password { get; init; } = null; - public DateTime? LockAt { get; init; } - public DateTime DueAt { get; init; } - public bool ShuffleAnswers { get; init; } = true; - public bool showCorrectAnswers { get; init; } = true; - public bool OneQuestionAtATime { get; init; } = false; - public string? LocalAssignmentGroupName { get; init; } - public int AllowedAttempts { get; init; } = -1; // -1 is infinite - // public bool ShowCorrectAnswers { get; init; } - // public int? TimeLimit { get; init; } = null; - // public string? HideResults { get; init; } = null; - // If null, students can see their results after any attempt. - // If “always”, students can never see their results. - // If “until_after_last_attempt”, students can only see results after their last attempt. (Only valid if allowed_attempts > 1). Defaults to null. - public IEnumerable Questions { get; init; } = - Enumerable.Empty(); - - public ulong? GetCanvasAssignmentGroupId(IEnumerable assignmentGroups) => - assignmentGroups - .FirstOrDefault(g => g.Name == LocalAssignmentGroupName)? - .CanvasId; - - public string GetDescriptionHtml() => MarkdownService.Render(Description); - - public string ToYaml() - { - var serializer = new SerializerBuilder().DisableAliases().Build(); - var yaml = serializer.Serialize(this); - return yaml; - } - - public string ToMarkdown() - { - var questionMarkdownArray = Questions.Select(q => q.ToMarkdown()).ToArray(); - var questionDelimiter = "\n\n---\n\n"; - var questionMarkdown = string.Join(questionDelimiter, questionMarkdownArray); - - return $@"Name: {Name} -LockAt: {LockAt} -DueAt: {DueAt} -Password: {Password} -ShuffleAnswers: {ShuffleAnswers.ToString().ToLower()} -ShowCorrectAnswers: {showCorrectAnswers.ToString().ToLower()} -OneQuestionAtATime: {OneQuestionAtATime.ToString().ToLower()} -AssignmentGroup: {LocalAssignmentGroupName} -AllowedAttempts: {AllowedAttempts} -Description: {Description} ---- -{questionMarkdown} -"; - } - - public static LocalQuiz ParseMarkdown(string input) - { - - var splitInput = input.Split("---\n"); - var settings = splitInput[0]; - var quizWithoutQuestions = getQuizWithOnlySettings(settings); - - var rawQuestions = splitInput[1..]; - var questions = rawQuestions - .Where(str => !string.IsNullOrWhiteSpace(str)) - .Select((q, i) => LocalQuizQuestion.ParseMarkdown(q, i)) - .ToArray(); - return quizWithoutQuestions with - { - Questions = questions - }; - } - - private static LocalQuiz getQuizWithOnlySettings(string settings) - { - - var name = extractLabelValue(settings, "Name"); - - var rawShuffleAnswers = extractLabelValue(settings, "ShuffleAnswers"); - var shuffleAnswers = bool.TryParse(rawShuffleAnswers, out bool parsedShuffleAnswers) - ? parsedShuffleAnswers - : throw new QuizMarkdownParseException($"Error with ShuffleAnswers: {rawShuffleAnswers}"); - - - var rawPassword = extractLabelValue(settings, "Password"); - var password = rawPassword == null || rawPassword.Trim() == string.Empty - ? null - : rawPassword; - - - var rawShowCorrectAnswers = extractLabelValue(settings, "ShowCorrectAnswers"); - var showCorrectAnswers = bool.TryParse(rawShowCorrectAnswers, out bool parsedShowCorrectAnswers) - ? parsedShowCorrectAnswers - : true; //default to true - - - var rawOneQuestionAtATime = extractLabelValue(settings, "OneQuestionAtATime"); - var oneQuestionAtATime = bool.TryParse(rawOneQuestionAtATime, out bool parsedOneQuestion) - ? parsedOneQuestion - : throw new QuizMarkdownParseException($"Error with oneQuestionAtATime: {rawOneQuestionAtATime}"); - - var rawAllowedAttempts = extractLabelValue(settings, "AllowedAttempts"); - var allowedAttempts = int.TryParse(rawAllowedAttempts, out int parsedAllowedAttempts) - ? parsedAllowedAttempts - : throw new QuizMarkdownParseException($"Error with AllowedAttempts: {rawAllowedAttempts}"); - - - var rawDueAt = extractLabelValue(settings, "DueAt"); - var dueAt = DateTime.TryParse(rawDueAt, out DateTime parsedDueAt) - ? parsedDueAt - : throw new QuizMarkdownParseException($"Error with DueAt: {rawDueAt}"); - - - var rawLockAt = extractLabelValue(settings, "LockAt"); - DateTime? lockAt = DateTime.TryParse(rawLockAt, out DateTime parsedLockAt) - ? parsedLockAt - : null; - - - var description = extractDescription(settings); - var assignmentGroup = extractLabelValue(settings, "AssignmentGroup"); - - return new LocalQuiz() - { - Name = name, - Description = description, - Password = password, - LockAt = lockAt, - DueAt = dueAt, - ShuffleAnswers = shuffleAnswers, - showCorrectAnswers = showCorrectAnswers, - OneQuestionAtATime = oneQuestionAtATime, - LocalAssignmentGroupName = assignmentGroup, - AllowedAttempts = allowedAttempts, - Questions = new LocalQuizQuestion[] { } - }; - } - - static string extractLabelValue(string input, string label) - { - string pattern = $@"{label}: (.*?)\n"; - Match match = Regex.Match(input, pattern); - - if (match.Success) - { - return match.Groups[1].Value; - } - - return string.Empty; - } - - static string extractDescription(string input) - { - string pattern = "Description: (.*?)$"; - Match match = Regex.Match(input, pattern, RegexOptions.Singleline); - - if (match.Success) - { - return match.Groups[1].Value; - } - - return string.Empty; - } -} - -public class QuizMarkdownParseException : Exception -{ - public QuizMarkdownParseException(string message) : base(message) - { - - } -} diff --git a/Management/Models/Local/Quiz/LocalQuizQuestion.cs b/Management/Models/Local/Quiz/LocalQuizQuestion.cs deleted file mode 100644 index cdf1631..0000000 --- a/Management/Models/Local/Quiz/LocalQuizQuestion.cs +++ /dev/null @@ -1,224 +0,0 @@ -using System.Text.RegularExpressions; - -using Akka.Util.Internal; - -namespace LocalModels; - -public record LocalQuizQuestion -{ - public string Text { get; init; } = string.Empty; - public string HtmlText => MarkdownService.Render(Text); - public string QuestionType { get; init; } = string.Empty; - public double Points { get; init; } - public IEnumerable Answers { get; init; } = - Enumerable.Empty(); - public IEnumerable MatchDistractors { get; init; } = []; - public string ToMarkdown() - { - var answerArray = Answers.Select(getAnswerMarkdown); - - - var distractorText = MatchDistractors - .Select(d => $"\n^ - {d}") - .Join(""); - - var answersText = string.Join("\n", answerArray) + distractorText; - var questionTypeIndicator = QuestionType == "essay" || QuestionType == "short_answer" ? QuestionType : ""; - - return $@"Points: {Points} -{Text} -{answersText}{questionTypeIndicator}"; - } - - private string getAnswerMarkdown(LocalQuizQuestionAnswer answer, int index) - { - var multilineMarkdownCompatibleText = answer.Text.StartsWith("```") - ? "\n" + answer.Text - : answer.Text; - - if (QuestionType == "multiple_answers") - { - var correctIndicator = answer.Correct ? "*" : " "; - var questionTypeIndicator = $"[{correctIndicator}] "; - - return $"{questionTypeIndicator}{multilineMarkdownCompatibleText}"; - } - else if (QuestionType == "matching") - { - return $"^ {answer.Text} - {answer.MatchedText}"; - } - else - { - var questionLetter = (char)(index + 97); - var correctIndicator = answer.Correct ? "*" : ""; - var questionTypeIndicator = $"{correctIndicator}{questionLetter}) "; - - return $"{questionTypeIndicator}{multilineMarkdownCompatibleText}"; - } - } - - private static readonly string[] _validFirstAnswerDelimiters = ["*a)", "a)", "*)", ")", "[ ]", "[*]", "^"]; - - public static LocalQuizQuestion ParseMarkdown(string input, int questionIndex) - { - var lines = input.Trim().Split("\n"); - var firstLineIsPoints = lines.First().Contains("points: ", StringComparison.CurrentCultureIgnoreCase); - - var textHasPoints = lines.Length > 0 - && lines.First().Contains(": ") - && lines.First().Split(": ").Length > 1 - && double.TryParse(lines.First().Split(": ")[1], out _); - - double points = firstLineIsPoints && textHasPoints ? double.Parse(lines.First().Split(": ")[1]) : 1; - - var linesWithoutPoints = firstLineIsPoints ? lines[1..] : lines; - - var linesWithoutAnswers = linesWithoutPoints - .TakeWhile( - (line, index) => - !_validFirstAnswerDelimiters.Any(prefix => line.TrimStart().StartsWith(prefix)) - ) - .ToArray(); - - - var questionType = getQuestionType(linesWithoutPoints, questionIndex); - - var questionTypesWithoutAnswers = new string[] { "essay", "short answer", "short_answer" }; - - var descriptionLines = questionTypesWithoutAnswers.Contains(questionType.ToLower()) - ? linesWithoutAnswers - .TakeWhile( - (line, index) => index != linesWithoutPoints.Length && !questionTypesWithoutAnswers.Contains(line.ToLower()) - ) - .ToArray() - : linesWithoutAnswers; - var description = string.Join("\n", descriptionLines); - - - - var typesWithAnswers = new string[] { "multiple_choice", "multiple_answers", "matching" }; - var answers = typesWithAnswers.Contains(questionType) - ? getAnswers(linesWithoutPoints, questionIndex, questionType) - : []; - - var distractors = questionType == "matching" - ? answers.Where(a => a.Text == "").Select(a => a.MatchedText ?? "").ToArray() - : []; - - var answersWithoutDistractors = questionType == "matching" - ? answers.Where(a => a.Text != "").ToArray() - : answers; - - - return new LocalQuizQuestion() - { - Text = description, - Points = points, - Answers = answersWithoutDistractors, - QuestionType = questionType, - MatchDistractors = distractors - }; - } - - private static string getQuestionType(string[] linesWithoutPoints, int questionIndex) - { - - if (linesWithoutPoints.Length == 0) - return ""; - if (linesWithoutPoints[^1].Equals("essay", StringComparison.CurrentCultureIgnoreCase)) - return "essay"; - if (linesWithoutPoints[^1].Equals("short answer", StringComparison.CurrentCultureIgnoreCase)) - return "short_answer"; - if (linesWithoutPoints[^1].Equals("short_answer", StringComparison.CurrentCultureIgnoreCase)) - return "short_answer"; - - var answerLines = getAnswerStringsWithMultilineSupport(linesWithoutPoints, questionIndex); - var firstAnswerLine = answerLines.First(); - var isMultipleChoice = - firstAnswerLine.StartsWith("a)") - || firstAnswerLine.StartsWith("*a)") - || firstAnswerLine.StartsWith("*)") - || firstAnswerLine.StartsWith(")"); - if (isMultipleChoice) - return "multiple_choice"; - - var isMultipleAnswer = - firstAnswerLine.StartsWith("[ ]") - || firstAnswerLine.StartsWith("[*]"); - - if (isMultipleAnswer) - return "multiple_answers"; - - var isMatching = answerLines.First().StartsWith("^"); - if (isMatching) - return "matching"; - - return ""; - } - - private static List getAnswerStringsWithMultilineSupport(string[] linesWithoutPoints, int questionIndex) - { - var indexOfAnswerStart = linesWithoutPoints - .ToList() - .FindIndex( - l => _validFirstAnswerDelimiters.Any(prefix => l.TrimStart().StartsWith(prefix)) - ); - if (indexOfAnswerStart == -1) - { - var debugLine = linesWithoutPoints.FirstOrDefault(l => l.Trim().Length > 0); - throw new QuizMarkdownParseException($"question {questionIndex + 1}: no answers when detecting question type on {debugLine}"); - } - - var answerLinesRaw = linesWithoutPoints[indexOfAnswerStart..]; - - var answerStartPattern = @"^(\*?[a-z]?\))|(?(), (acc, line) => - { - var isNewAnswer = Regex.IsMatch(line, answerStartPattern); - if (isNewAnswer) - { - acc.Add(line); - return acc; - } - - if (acc.Count != 0) // Append to the previous line if there is one - acc[^1] += "\n" + line; - else - acc.Add(line); - - return acc; - }); - return answerLines; - } - - private static LocalQuizQuestionAnswer[] getAnswers(string[] linesWithoutPoints, int questionIndex, string questionType) - { - var answerLines = getAnswerStringsWithMultilineSupport(linesWithoutPoints, questionIndex); - - var answers = answerLines - .Select((a, i) => LocalQuizQuestionAnswer.ParseMarkdown(a, questionType)) - .ToArray(); - - return answers; - } -} - -public static class QuestionType -{ - public static readonly string MULTIPLE_ANSWERS = "multiple_answers"; - public static readonly string MULTIPLE_CHOICE = "multiple_choice"; - public static readonly string ESSAY = "essay"; - public static readonly string SHORT_ANSWER = "short_answer"; - public static readonly string MATCHING = "matching"; - - // possible support for: calculated, file_upload, fill_in_multiple_blanks, matching, multiple_dropdowns, numerical, text_only, true_false, - public static readonly IEnumerable AllTypes = new string[] - { - MULTIPLE_ANSWERS, - MULTIPLE_CHOICE, - ESSAY, - SHORT_ANSWER, - MATCHING - - }; -} diff --git a/Management/Models/Local/Quiz/LocalQuizQuestionAnswer.cs b/Management/Models/Local/Quiz/LocalQuizQuestionAnswer.cs deleted file mode 100644 index 7a8044e..0000000 --- a/Management/Models/Local/Quiz/LocalQuizQuestionAnswer.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Text.RegularExpressions; - -namespace LocalModels; - -public record LocalQuizQuestionAnswer -{ - //correct gets a weight of 100 in canvas - public bool Correct { get; init; } - public string Text { get; init; } = string.Empty; - - public string? MatchedText { get; init; } - - public string HtmlText => MarkdownService.Render(Text); - - public static LocalQuizQuestionAnswer ParseMarkdown(string input, string questionType) - { - var isCorrect = input[0] == '*' || input[1] == '*'; - - if (questionType == QuestionType.MATCHING) - { - - string matchingPattern = @"^\^"; - var textWithoutMatchDelimiter = Regex.Replace(input, matchingPattern, string.Empty); - - var leftRightDelimiter = " - "; - return new LocalQuizQuestionAnswer() - { - Correct = true, - Text = textWithoutMatchDelimiter.Split(leftRightDelimiter)[0].Trim(), - MatchedText = string.Join( - leftRightDelimiter, - textWithoutMatchDelimiter - .Split(leftRightDelimiter)[1..] - .Select(a => a.Trim()) - .Where(a => a != "") - ).Trim(), - }; - } - - string startingQuestionPattern = @"^(\*?[a-z]?\))|\[\s*\]|\[\*\]|\^ "; - - int replaceCount = 0; - var text = Regex - .Replace(input, startingQuestionPattern, (m) => replaceCount++ == 0 ? "" : m.Value) - .Trim(); - return new LocalQuizQuestionAnswer() - { - Correct = isCorrect, - Text = text, - }; - } -} diff --git a/Management/Services/Actors/CoursePlannerActor.cs b/Management/Services/Actors/CoursePlannerActor.cs deleted file mode 100644 index 73efe13..0000000 --- a/Management/Services/Actors/CoursePlannerActor.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Akka.Actor; - -using Management.Services; - -using Microsoft.Extensions.DependencyInjection; - -public class CoursePlannerActor: ReceiveActor -{ - private readonly IServiceProvider serviceProvider; - private readonly IServiceScope scope; - private readonly MyLogger logger; - public CoursePlannerActor(IServiceProvider serviceProviderArg) - { - serviceProvider = serviceProviderArg; - scope = serviceProvider.CreateScope(); - logger = scope.ServiceProvider.GetRequiredService>(); - } -} \ No newline at end of file diff --git a/Management/Services/Actors/LocalStorageActor.cs b/Management/Services/Actors/LocalStorageActor.cs deleted file mode 100644 index a651b78..0000000 --- a/Management/Services/Actors/LocalStorageActor.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Akka.Actor; - -using LocalModels; - -using Management.Services; - -using Microsoft.Extensions.DependencyInjection; - -public class LocalStorageActor : ReceiveActor -{ - private readonly IServiceProvider serviceProvider; - private readonly IServiceScope scope; - private readonly MyLogger logger; - private readonly FileStorageService storage; - - private DateTime? cacheTime { get; set; } = null; - private IEnumerable? cachedCourses { get; set; } = null; - private readonly int cacheSeconds = 2; - - public LocalStorageActor(IServiceProvider serviceProviderArg) - { - serviceProvider = serviceProviderArg; - scope = serviceProvider.CreateScope(); - logger = scope.ServiceProvider.GetRequiredService>(); - storage = scope.ServiceProvider.GetRequiredService(); - - Receive(m => - { - storage - .GetEmptyDirectories() - .PipeTo(Sender); - }); - - ReceiveAsync(async m => - { - var secondsFromLastLoad = (DateTime.Now - cacheTime)?.TotalSeconds; - - if (cachedCourses != null && secondsFromLastLoad < cacheSeconds) - { - logger.Log("returning cached courses from file"); - Sender.Tell(cachedCourses); - return; - } - cachedCourses = await storage.LoadSavedCourses(); - cacheTime = DateTime.Now; - Sender.Tell(cachedCourses); - }); - - ReceiveAsync(async m => - { - cacheTime = null; - cachedCourses = null; - await storage.SaveCourseAsync(m.Course, m.PreviouslyStoredCourse); - }); - } -} - -public record EmptyDirectoryAsk(); -public record SavedCoursesAsk(); - -public record SaveCoursesRequest(LocalCourse Course, LocalCourse? PreviouslyStoredCourse); -public record SaveCoursesResponseSuccess(); \ No newline at end of file diff --git a/Management/Services/Actors/LocalStorageActorWrapper.cs b/Management/Services/Actors/LocalStorageActorWrapper.cs deleted file mode 100644 index 43cc88d..0000000 --- a/Management/Services/Actors/LocalStorageActorWrapper.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Akka.Actor; - -using LocalModels; - -public class LocalStorageActorWrapper(IActorRef storageActor) : IFileStorageManager -{ - private readonly IActorRef storageActor = storageActor; - - public async Task> GetEmptyDirectories() - { - return await storageActor.Ask>(new EmptyDirectoryAsk()); - } - - public async Task> LoadSavedCourses() - { - return await storageActor.Ask>(new SavedCoursesAsk()); - } - - public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse) - { - await storageActor.Ask(new SaveCoursesRequest(course, previouslyStoredCourse)); - } -} \ No newline at end of file diff --git a/Management/Services/AkkaService.cs b/Management/Services/AkkaService.cs deleted file mode 100644 index d5616e3..0000000 --- a/Management/Services/AkkaService.cs +++ /dev/null @@ -1,60 +0,0 @@ - -using Akka.Actor; -using Akka.DependencyInjection; - -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; - -namespace Management.Services; - - -public class AkkaService( - IServiceProvider serviceProvider, - IHostApplicationLifetime appLifetime, - IConfiguration configuration -) : IHostedService -{ - private ActorSystem? actorSystem; - private readonly IConfiguration configuration = configuration; - private readonly IServiceProvider serviceProvider = serviceProvider; - private readonly IHostApplicationLifetime applicationLifetime = appLifetime; - public IActorRef? CoursePlannerActor { get; private set; } - public IActorRef? StorageActor { get; private set; } - - public Task StartAsync(CancellationToken cancellationToken) - { - var bootstrap = BootstrapSetup.Create(); - var dependencyInjectionSetup = DependencyResolverSetup.Create(serviceProvider); - - var mergedSystemSetup = bootstrap.And(dependencyInjectionSetup); - - actorSystem = ActorSystem.Create("canavas-management-actor-system", mergedSystemSetup); - - var canvasQueueProps = DependencyResolver.For(actorSystem).Props(); - CoursePlannerActor = actorSystem.ActorOf(canvasQueueProps, "canvasQueue"); - var localStorageProps = DependencyResolver.For(actorSystem).Props(); - StorageActor = actorSystem.ActorOf(localStorageProps, "localStorage"); - - // crash if the actor system crashes, awaiting never returns... - actorSystem.WhenTerminated.ContinueWith(tr => - { - applicationLifetime.StopApplication(); - }); - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - - // public void Tell(object message) - // { - // userSessionSupervisor?.Tell(message); - // } - - // public Task Ask(object message) - // { - // return userSessionSupervisor.Ask(message); - // } - -} diff --git a/Management/Services/Canvas/CanvasAssignmentGroupService.cs b/Management/Services/Canvas/CanvasAssignmentGroupService.cs deleted file mode 100644 index 91d7d9d..0000000 --- a/Management/Services/Canvas/CanvasAssignmentGroupService.cs +++ /dev/null @@ -1,84 +0,0 @@ -using CanvasModel.Assignments; -using LocalModels; -using RestSharp; - -namespace Management.Services.Canvas; - - -public interface ICanvasAssignmentGroupService -{ - Task> GetAll(ulong courseId); - Task Create(ulong canvasCourseId, LocalAssignmentGroup localAssignmentGroup); - Task Update(ulong canvasCourseId, LocalAssignmentGroup localAssignmentGroup); - -} -public class CanvasAssignmentGroupService: ICanvasAssignmentGroupService -{ - private readonly IWebRequestor webRequestor; - private readonly CanvasServiceUtils utils; - private readonly MyLogger logger; - - public CanvasAssignmentGroupService( - IWebRequestor webRequestor, - CanvasServiceUtils utils, - MyLogger logger - ) - { - this.webRequestor = webRequestor; - this.utils = utils; - this.logger = logger; - } - public async Task> GetAll(ulong courseId) - { - var url = $"courses/{courseId}/assignment_groups"; - var request = new RestRequest(url); - var assignmentResponse = await utils.PaginatedRequest>(request); - return assignmentResponse.SelectMany( - assignments => assignments - ); - } - - public async Task Create( - ulong canvasCourseId, - LocalAssignmentGroup localAssignmentGroup - ) - { - logger.Log($"creating assignment group: {localAssignmentGroup.Name}"); - var url = $"courses/{canvasCourseId}/assignment_groups"; - var request = new RestRequest(url); - var body = new - { - name = localAssignmentGroup.Name, - group_weight = localAssignmentGroup.Weight, - }; - request.AddBody(body); - - var (canvasAssignmentGroup, response) = await webRequestor.PostAsync(request); - if (canvasAssignmentGroup == null) - throw new Exception("created canvas assignment group was null"); - - return localAssignmentGroup with - { - CanvasId = canvasAssignmentGroup.Id - }; - } - public async Task Update( - ulong canvasCourseId, - LocalAssignmentGroup localAssignmentGroup - ) - { - logger.Log($"updating assignment group: {localAssignmentGroup.Name}"); - if (localAssignmentGroup.CanvasId == null) - throw new Exception("cannot update assignment group if canvas id is null"); - var url = $"courses/{canvasCourseId}/assignment_groups/{localAssignmentGroup.CanvasId}"; - var request = new RestRequest(url); - var body = new - { - name = localAssignmentGroup.Name, - group_weight = localAssignmentGroup.Weight, - }; - request.AddBody(body); - - await webRequestor.PutAsync(request); - } -} diff --git a/Management/Services/Canvas/CanvasAssignmentService.cs b/Management/Services/Canvas/CanvasAssignmentService.cs deleted file mode 100644 index 786d69b..0000000 --- a/Management/Services/Canvas/CanvasAssignmentService.cs +++ /dev/null @@ -1,183 +0,0 @@ -using CanvasModel.Assignments; -using LocalModels; -using RestSharp; - -namespace Management.Services.Canvas; -public interface ICanvasAssignmentService -{ - Task> GetAll(ulong courseId); - Task Create( - ulong canvasCourseId, - LocalAssignment localAssignment, - ulong? canvasAssignmentGroupId - ); - Task Update( - ulong courseId, - ulong canvasAssignmentId, - LocalAssignment localAssignment, - ulong? canvasAssignmentGroupId - ); - Task Delete(ulong courseId, ulong assignmentCanvasId, string assignmentName); - Task CreateRubric(ulong courseId, ulong assignmentCanvasId, LocalAssignment localAssignment); -} -public class CanvasAssignmentService( - IWebRequestor webRequestor, - CanvasServiceUtils utils, - MyLogger logger - ): ICanvasAssignmentService -{ - private readonly IWebRequestor webRequestor = webRequestor; - private readonly CanvasServiceUtils utils = utils; - private readonly MyLogger log = logger; - - public async Task> GetAll(ulong courseId) - { - var url = $"courses/{courseId}/assignments"; - var request = new RestRequest(url); - // request.AddParameter("include[]", "overrides"); - var assignmentResponse = await utils.PaginatedRequest>(request); - return assignmentResponse.SelectMany( - assignments => - assignments.Select( - a => a with { DueAt = a.DueAt?.ToLocalTime(), LockAt = a.LockAt?.ToLocalTime() } - ).ToArray() - ).ToArray(); - } - - public async Task Create( - ulong canvasCourseId, - LocalAssignment localAssignment, - ulong? canvasAssignmentGroupId - ) - { - log.Log($"creating assignment: {localAssignment.Name}"); - var url = $"courses/{canvasCourseId}/assignments"; - var request = new RestRequest(url); - var body = new - { - name = localAssignment.Name, - submission_types = localAssignment.SubmissionTypes.Select(t => t.ToString()), - allowed_extensions = localAssignment.AllowedFileUploadExtensions.Select(e => e.ToString()), - description = localAssignment.GetDescriptionHtml(), - due_at = localAssignment.DueAt, - lock_at = localAssignment.LockAt, - points_possible = localAssignment.PointsPossible, - assignment_group_id = canvasAssignmentGroupId, - }; - var bodyObj = new { assignment = body }; - request.AddBody(bodyObj); - var (canvasAssignment, response) = await webRequestor.PostAsync(request); - if (canvasAssignment == null) - throw new Exception("created canvas assignment was null"); - - - await CreateRubric(canvasCourseId, canvasAssignment.Id, localAssignment); - - return canvasAssignment.Id; - } - - public async Task Update( - ulong courseId, - ulong canvasAssignmentId, - LocalAssignment localAssignment, - ulong? canvasAssignmentGroupId - ) - { - log.Log($"updating assignment: {localAssignment.Name}"); - var url = $"courses/{courseId}/assignments/{canvasAssignmentId}"; - var request = new RestRequest(url); - var body = new - { - name = localAssignment.Name, - submission_types = localAssignment.SubmissionTypes.Select(t => t.ToString()), - allowed_extensions = localAssignment.AllowedFileUploadExtensions.Select(e => e.ToString()), - description = localAssignment.GetDescriptionHtml(), - due_at = localAssignment.DueAt, - lock_at = localAssignment.LockAt, - points_possible = localAssignment.PointsPossible, - assignment_group_id = canvasAssignmentGroupId, - }; - - var bodyObj = new { assignment = body }; - request.AddBody(bodyObj); - - await webRequestor.PutAsync(request); - - await CreateRubric(courseId, canvasAssignmentId, localAssignment); - } - - public async Task Delete(ulong courseId, ulong assignmentCanvasId, string assignmentName) - { - log.Log($"deleting assignment from canvas {assignmentName}"); - var url = $"courses/{courseId}/assignments/{assignmentCanvasId}"; - var request = new RestRequest(url); - var response = await webRequestor.DeleteAsync(request); - if (!response.IsSuccessful) - { - log.Log(url); - throw new Exception("Failed to delete assignment"); - } - } - - public async Task CreateRubric(ulong courseId, ulong assignmentCanvasId, LocalAssignment localAssignment) - { - - var criterion = new Dictionary(); - - var i = 0; - foreach (var rubricItem in localAssignment.Rubric) - { - var ratings = new Dictionary - { - { 0, new { description = "Full Marks", points = rubricItem.Points } }, - { 1, new { description = "No Marks", points = 0 } }, - }; - criterion[i] = new - { - description = rubricItem.Label, - points = rubricItem.Points, - ratings - }; - i++; - } - - // https://canvas.instructure.com/doc/api/rubrics.html#method.rubrics.create - var body = new - { - rubric_association_id = assignmentCanvasId, - rubric = new - { - title = $"Rubric for Assignment: {localAssignment.Name}", - association_id = assignmentCanvasId, - association_type = "Assignment", - use_for_grading = true, - criteria = criterion, - }, - rubric_association = new - { - association_id = assignmentCanvasId, - association_type = "Assignment", - purpose = "grading", - use_for_grading = true, - } - }; - var creationUrl = $"courses/{courseId}/rubrics"; - var rubricCreationRequest = new RestRequest(creationUrl); - rubricCreationRequest.AddBody(body); - var (rubricCreationResponse, _) = await webRequestor.PostAsync( - rubricCreationRequest - ); - - if (rubricCreationResponse == null) - throw new Exception("failed to create rubric before association"); - - var assignmentPointCorrectionBody = new - { - assignment = new { points_possible = localAssignment.PointsPossible } - }; - var adjustmentUrl = $"courses/{courseId}/assignments/{assignmentCanvasId}"; - var pointAdjustmentRequest = new RestRequest(adjustmentUrl); - pointAdjustmentRequest.AddBody(assignmentPointCorrectionBody); - var (_, _) = await webRequestor.PutAsync(pointAdjustmentRequest); - } -} diff --git a/Management/Services/Canvas/CanvasCoursePageService.cs b/Management/Services/Canvas/CanvasCoursePageService.cs deleted file mode 100644 index 985e899..0000000 --- a/Management/Services/Canvas/CanvasCoursePageService.cs +++ /dev/null @@ -1,94 +0,0 @@ - -using CanvasModel.Courses; -using CanvasModel.Modules; -using CanvasModel.Pages; -using LocalModels; -using RestSharp; - -namespace Management.Services.Canvas; -public interface ICanvasCoursePageService -{ - Task> GetAll(ulong courseId); - Task Create(ulong canvasCourseId, LocalCoursePage localCourse); - Task Update(ulong courseId, ulong canvasPageId, LocalCoursePage localCoursePage); - Task Delete(ulong courseId, ulong canvasPageId); -} -public class CanvasCoursePageService( - IWebRequestor webRequestor, - CanvasServiceUtils utils, - MyLogger logger - ) : ICanvasCoursePageService -{ - private readonly IWebRequestor webRequestor = webRequestor; - private readonly CanvasServiceUtils utils = utils; - private readonly MyLogger log = logger; - - - - public async Task> GetAll(ulong courseId) - { - var url = $"courses/{courseId}/pages"; - var request = new RestRequest(url); - // request.AddParameter("include[]", "overrides"); - var pagesResponse = await utils.PaginatedRequest>(request); - return pagesResponse.SelectMany( - pages => pages - ); - } - - - public async Task Create( - ulong canvasCourseId, - LocalCoursePage localCourse - ) - { - log.Log($"creating course page: {localCourse.Name}"); - var url = $"courses/{canvasCourseId}/pages"; - var request = new RestRequest(url); - var body = new - { - title = localCourse.Name, - body = localCourse.GetBodyHtml() - }; - var bodyObj = new { wiki_page = body }; - request.AddBody(bodyObj); - var (canvasPage, response) = await webRequestor.PostAsync(request); - if (canvasPage == null) - throw new Exception("created canvas course page was null"); - - return canvasPage; - } - - public async Task Update( - ulong courseId, - ulong canvasPageId, - LocalCoursePage localCoursePage - ) - { - log.Log($"updating course page: {localCoursePage.Name}"); - var url = $"courses/{courseId}/pages/{canvasPageId}"; - var request = new RestRequest(url); - var body = new - { - title = localCoursePage.Name, - body = localCoursePage.GetBodyHtml() - }; - var bodyObj = new { wiki_page = body }; - request.AddBody(bodyObj); - - await webRequestor.PutAsync(request); - } - - public async Task Delete(ulong courseId, ulong canvasPageId) - { - log.Log($"deleting page from canvas {canvasPageId}"); - var url = $"courses/{courseId}/pages/{canvasPageId}"; - var request = new RestRequest(url); - var response = await webRequestor.DeleteAsync(request); - if (!response.IsSuccessful) - { - log.Log(url); - throw new Exception("Failed to delete canvas course page"); - } - } -} diff --git a/Management/Services/Canvas/CanvasModuleService.cs b/Management/Services/Canvas/CanvasModuleService.cs deleted file mode 100644 index 53ff61c..0000000 --- a/Management/Services/Canvas/CanvasModuleService.cs +++ /dev/null @@ -1,115 +0,0 @@ - -using CanvasModel.Modules; -using LocalModels; -using RestSharp; - -namespace Management.Services.Canvas; - - -public interface ICanvasModuleService -{ - Task> GetModules(ulong courseId); - Task CreateModule(ulong courseId, string name); - Task UpdateModule(ulong courseId, ulong moduleId, string name, uint position); - Task> GetModuleItems(ulong courseId, ulong moduleId); - Task>> GetAllModulesItems(ulong courseId, IEnumerable modules); - -} - -public class CanvasModuleService: ICanvasModuleService -{ - - private readonly IWebRequestor webRequestor; - private readonly CanvasServiceUtils utils; - - public CanvasModuleService(IWebRequestor webRequestor, CanvasServiceUtils utils) - { - this.webRequestor = webRequestor; - this.utils = utils; - } - - - public async Task> GetModules(ulong courseId) - { - var url = $"courses/{courseId}/modules"; - var request = new RestRequest(url); - var modules = await utils.PaginatedRequest>(request); - return modules.SelectMany(c => c).ToArray(); - } - - public async Task CreateModule(ulong courseId, string name) - { - Console.WriteLine($"Creating Module: {name}"); - var url = $"courses/{courseId}/modules"; - var request = new RestRequest(url); - var body = new - { - module = new - { - name - } - }; - request.AddBody(body); - - var (newModule, _) = await webRequestor.PostAsync(request); - return newModule ?? throw new Exception($"failed to create new canvas module {name}"); - } - - public async Task UpdateModule(ulong courseId, ulong moduleId, string name, uint position) - { - Console.WriteLine($"Updating Module: {name}"); - var url = $"courses/{courseId}/modules/{moduleId}"; - var body = new { module = new { name = name, position = position } }; - var request = new RestRequest(url); - request.AddBody(body); - - await webRequestor.PutAsync(request); - } - - public async Task> GetModuleItems(ulong courseId, ulong moduleId) - { - var url = $"courses/{courseId}/modules/{moduleId}/items?include[]=content_details"; - var request = new RestRequest(url); - var (items, response) = await webRequestor.GetAsync>(request); - if (items == null) - throw new Exception($"Error getting canvas module items for {url}"); - return items.Select(i => - i with - { - ContentDetails = i.ContentDetails == null - ? null - : i.ContentDetails with - { - DueAt = i.ContentDetails.DueAt?.ToLocalTime(), - LockAt = i.ContentDetails.LockAt?.ToLocalTime(), - } - } - ); - } - - public async Task>> GetAllModulesItems( - ulong courseId, - IEnumerable modules - ) - { - var itemsTasks = modules.Select( - async (m) => - { - var items = await GetModuleItems(courseId, m.Id); - return (m, items); - } - ); - - var output = new Dictionary>(); - var itemTasksResult = await Task.WhenAll(itemsTasks); - foreach (var (module, items) in itemTasksResult) - { - if (module == null || items == null) - throw new Exception( - "i'm not sure how we got here, but module and items are null after looking up module items" - ); - output[module] = items; - } - return output; - } -} diff --git a/Management/Services/Canvas/CanvasQuizService.cs b/Management/Services/Canvas/CanvasQuizService.cs deleted file mode 100644 index 5458f09..0000000 --- a/Management/Services/Canvas/CanvasQuizService.cs +++ /dev/null @@ -1,213 +0,0 @@ -using CanvasModel.Quizzes; - -using LocalModels; - -using RestSharp; - -namespace Management.Services.Canvas; - -public interface ICanvasQuizService -{ - Task> GetAll(ulong courseId); - Task Create(ulong canvasCourseId, LocalQuiz localQuiz, ulong? canvasAssignmentGroupId); - Task CreateQuizQuestions(ulong canvasCourseId, ulong canvasQuizId, LocalQuiz localQuiz); - -} -public class CanvasQuizService( - IWebRequestor webRequestor, - CanvasServiceUtils utils, - ICanvasAssignmentService assignments, - ILogger logger -) : ICanvasQuizService -{ - private readonly IWebRequestor webRequestor = webRequestor; - private readonly CanvasServiceUtils utils = utils; - private readonly ICanvasAssignmentService assignments = assignments; - private readonly ILogger logger = logger; - - public async Task> GetAll(ulong courseId) - { - var url = $"courses/{courseId}/quizzes"; - var request = new RestRequest(url); - var quizResponse = await utils.PaginatedRequest>(request); - return quizResponse.SelectMany( - quizzes => - quizzes.Select( - a => a with { DueAt = a.DueAt?.ToLocalTime(), LockAt = a.LockAt?.ToLocalTime() } - ) - ); - } - - public async Task Create( - ulong canvasCourseId, - LocalQuiz localQuiz, - ulong? canvasAssignmentGroupId - ) - { - using var activity = DiagnosticsConfig.Source.StartActivity("Creating all canvas quiz"); - activity?.SetCustomProperty("localQuiz", localQuiz); - activity?.SetTag("canvas syncronization", true); - Console.WriteLine($"Creating Quiz {localQuiz.Name}"); - - var url = $"courses/{canvasCourseId}/quizzes"; - var body = new - { - quiz = new - { - title = localQuiz.Name, - description = localQuiz.GetDescriptionHtml(), - // assignment_group_id = "quiz", // TODO: support specific assignment groups - // time_limit = localQuiz.TimeLimit, - shuffle_answers = localQuiz.ShuffleAnswers, - access_code = localQuiz.Password, - show_correct_answers = localQuiz.showCorrectAnswers, - // hide_results = localQuiz.HideResults, - allowed_attempts = localQuiz.AllowedAttempts, - one_question_at_a_time = false, - cant_go_back = false, - due_at = localQuiz.DueAt, - lock_at = localQuiz.LockAt, - assignment_group_id = canvasAssignmentGroupId, - } - }; - var request = new RestRequest(url); - request.AddBody(body); - var (canvasQuiz, response) = await webRequestor.PostAsync(request); - if (canvasQuiz == null) - throw new Exception("Created canvas quiz was null"); - activity?.SetCustomProperty("canvasQuizId", canvasQuiz.Id); - - await CreateQuizQuestions(canvasCourseId, canvasQuiz.Id, localQuiz); - return canvasQuiz.Id; - } - - public async Task CreateQuizQuestions( - ulong canvasCourseId, - ulong canvasQuizId, - LocalQuiz localQuiz - ) - { - using var activity = DiagnosticsConfig.Source.StartActivity("Creating all quiz questions"); - activity?.SetCustomProperty("canvasQuizId", canvasQuizId); - activity?.SetTag("canvas syncronization", true); - - - var tasks = localQuiz.Questions.Select( - async (q, i) => await createQuestionOnly(canvasCourseId, canvasQuizId, q, i) - ).ToArray(); - var questionAndPositions = await Task.WhenAll(tasks); - await hackFixQuestionOrdering(canvasCourseId, canvasQuizId, questionAndPositions); - await hackFixRedundantAssignments(canvasCourseId); - } - - private async Task hackFixRedundantAssignments(ulong canvasCourseId) - { - using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing redundant quiz assignments that are auto-created"); - activity?.SetTag("canvas syncronization", true); - - var canvasAssignments = await assignments.GetAll(canvasCourseId); - var assignmentsToDelete = canvasAssignments - .Where( - assignment => - !assignment.IsQuizAssignment - && assignment.SubmissionTypes.Contains(AssignmentSubmissionType.ONLINE_QUIZ) - ) - .ToArray(); - var tasks = assignmentsToDelete.Select( - async (a) => - { - await assignments.Delete( - canvasCourseId, - a.Id, - a.Name - ); - } - ).ToArray(); - await Task.WhenAll(tasks); - } - - private async Task hackFixQuestionOrdering(ulong canvasCourseId, ulong canvasQuizId, IEnumerable<(CanvasQuizQuestion question, int position)> questionAndPositions) - { - using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing question ordering with reorder"); - activity?.SetCustomProperty("canvasQuizId", canvasQuizId); - activity?.SetTag("canvas syncronization", true); - - var order = questionAndPositions.OrderBy(t => t.position).Select(tuple => - { - return new - { - type = "question", - id = tuple.question.Id.ToString(), - }; - }).ToArray(); - - var url = $"courses/{canvasCourseId}/quizzes/{canvasQuizId}/reorder"; - - var request = new RestRequest(url); - request.AddBody(new { order }); - var response = await webRequestor.PostAsync(request); - - if (!response.IsSuccessStatusCode) - throw new NullReferenceException("error re-ordering questions, reorder response is not successfull"); - } - - private async Task<(CanvasQuizQuestion question, int position)> createQuestionOnly( - ulong canvasCourseId, - ulong canvasQuizId, - LocalQuizQuestion q, - int position - ) - { - using var activity = DiagnosticsConfig.Source.StartActivity("creating quiz question"); - activity?.SetTag("canvas syncronization", true); - activity?.SetTag("localQuestion", q); - activity?.SetCustomProperty("localQuestion", q); - activity?.SetTag("success", false); - - var url = $"courses/{canvasCourseId}/quizzes/{canvasQuizId}/questions"; - var answers = getAnswers(q); - - var body = new - { - question = new - { - question_text = q.HtmlText, - question_type = q.QuestionType + "_question", - points_possible = q.Points, - position, - matching_answer_incorrect_matches = string.Join("\n", q.MatchDistractors), - answers - } - }; - Console.WriteLine(JsonSerializer.Serialize(q)); - Console.WriteLine(JsonSerializer.Serialize(body)); - var request = new RestRequest(url); - request.AddBody(body); - - var (newQuestion, response) = await webRequestor.PostAsync(request); - if (newQuestion == null) - throw new NullReferenceException("error creating new question, created question is null"); - - - activity?.SetCustomProperty("canvasQuizId", newQuestion.Id); - activity?.SetTag("success", true); - - return (newQuestion, position); - } - - private static object[] getAnswers(LocalQuizQuestion q) - { - if (q.QuestionType == QuestionType.MATCHING) - return q.Answers - .Select(a => new - { - answer_match_left = a.Text, - answer_match_right = a.MatchedText, - }) - .ToArray(); - - return q.Answers - .Select(a => new { answer_html = a.HtmlText, answer_weight = a.Correct ? 100 : 0 }) - .ToArray(); - } -} diff --git a/Management/Services/Canvas/CanvasRequests/CanvasRubricCreationResponse.cs b/Management/Services/Canvas/CanvasRequests/CanvasRubricCreationResponse.cs deleted file mode 100644 index 65cffa4..0000000 --- a/Management/Services/Canvas/CanvasRequests/CanvasRubricCreationResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -using CanvasModel.Assignments; - -public record CanvasRubricCreationResponse -{ - public required CanvasRubric rubric { get; set; } - public CanvasRubricAssociation? rubric_association { get; set; } -} diff --git a/Management/Services/Canvas/CanvasService.cs b/Management/Services/Canvas/CanvasService.cs deleted file mode 100644 index 736a03f..0000000 --- a/Management/Services/Canvas/CanvasService.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System.Security.Policy; - -using CanvasModel; -using CanvasModel.Assignments; -using CanvasModel.Courses; -using CanvasModel.Enrollments; -using CanvasModel.EnrollmentTerms; -using CanvasModel.Modules; -using CanvasModel.Pages; -using RestSharp; - -namespace Management.Services.Canvas; - -public interface ICanvasService -{ - ICanvasAssignmentService Assignments { get; } - ICanvasAssignmentGroupService AssignmentGroups { get; } - ICanvasModuleService Modules { get; } - ICanvasQuizService Quizzes { get; } - ICanvasCoursePageService Pages { get; } - Task> GetTerms(); - Task> GetCourses(ulong termId); - Task GetCourse(ulong courseId); - Task> GetCurrentTermsFor(DateTime? queryDate = null); - Task UpdateModuleItem(ulong canvasCourseId, ulong canvasModuleId, CanvasModuleItem item); - Task CreateModuleItem(ulong canvasCourseId, ulong canvasModuleId, string title, string type, ulong contentId); - Task CreateModuleItem(ulong canvasCourseId, ulong canvasModuleId, string title, string type, string contentId); - Task CreatePageModuleItem(ulong canvasCourseId, ulong canvasModuleId, string title, CanvasPage canvasPage); - Task> GetEnrolledStudents(ulong canvasCourseId); -} - -public class CanvasService( - IWebRequestor webRequestor, - CanvasServiceUtils utils, - ICanvasAssignmentService Assignments, - ICanvasAssignmentGroupService AssignmentGroups, - ICanvasModuleService Modules, - ICanvasQuizService Quizzes, - ICanvasCoursePageService Pages, - MyLogger logger -) : ICanvasService -{ - private readonly IWebRequestor webRequestor = webRequestor; - private readonly CanvasServiceUtils utils = utils; - private readonly MyLogger logger = logger; - - public ICanvasAssignmentService Assignments { get; } = Assignments; - public ICanvasAssignmentGroupService AssignmentGroups { get; } = AssignmentGroups; - public ICanvasModuleService Modules { get; } = Modules; - public ICanvasQuizService Quizzes { get; } = Quizzes; - public ICanvasCoursePageService Pages { get; } = Pages; - - public async Task> GetTerms() - { - var url = $"accounts/10/terms"; - - var request = new RestRequest(url); - var termResponses = await utils.PaginatedRequest(request); - var terms = termResponses.Select(r => r.EnrollmentTerms).SelectMany(s => s).ToArray(); - return terms; - } - - public async Task> GetCourses(ulong termId) - { - var url = $"courses"; - var request = new RestRequest(url); - var coursesResponse = await utils.PaginatedRequest>(request); - return coursesResponse.SelectMany(c => c).Where(c => c.EnrollmentTermId == termId).ToArray(); - } - - public async Task GetCourse(ulong courseId) - { - var url = $"courses/{courseId}"; - var request = new RestRequest(url); - var (data, response) = await webRequestor.GetAsync(request); - - if (data == null) - { - logger.Error(response?.Content ?? ""); - logger.Error(response?.ResponseUri?.ToString() ?? ""); - throw new Exception("error getting course from canvas"); - } - return data; - } - - - - public async Task> GetCurrentTermsFor( - DateTime? _queryDate = null - ) - { - DateTime queryDate = _queryDate ?? DateTime.Now; - - var terms = await GetTerms(); - - var currentTerms = terms - .Where(t => t.EndAt != null && t.EndAt > queryDate && t.EndAt < queryDate.AddYears(1)) - .Take(3) - .OrderBy(t => t.StartAt); - - return currentTerms; - } - - public async Task UpdateModuleItem( - ulong canvasCourseId, - ulong canvasModuleId, - CanvasModuleItem item - ) - { - logger.Log($"updating module item {item.Title}"); - var url = $"courses/{canvasCourseId}/modules/{canvasModuleId}/items/{item.Id}"; - var body = new { module_item = new { title = item.Title, position = item.Position } }; - var request = new RestRequest(url); - request.AddBody(body); - - var (newItem, response) = await webRequestor.PutAsync(request); - if (newItem == null) - throw new Exception("something went wrong updating module item"); - } - - public async Task CreateModuleItem( - ulong canvasCourseId, - ulong canvasModuleId, - string title, - string type, - ulong contentId - ) - { - logger.Log($"creating new module item {title}"); - var url = $"courses/{canvasCourseId}/modules/{canvasModuleId}/items"; - var body = new - { - module_item = new - { - title, - type = type.ToString(), - content_id = contentId, - } - }; - var request = new RestRequest(url); - request.AddBody(body); - - var (newItem, _response) = await webRequestor.PostAsync(request); - if (newItem == null) - throw new Exception("something went wrong updating module item"); - } - public async Task CreateModuleItem( - ulong canvasCourseId, - ulong canvasModuleId, - string title, - string type, - string contentId - ) - { - logger.Log($"creating new module item {title}"); - var url = $"courses/{canvasCourseId}/modules/{canvasModuleId}/items"; - var body = new - { - module_item = new - { - title, - type = type.ToString(), - content_id = contentId, - } - }; - var request = new RestRequest(url); - request.AddBody(body); - - var (newItem, _response) = await webRequestor.PostAsync(request); - if (newItem == null) - throw new Exception("something went wrong updating module item with string content id"); - } - public async Task CreatePageModuleItem( - ulong canvasCourseId, - ulong canvasModuleId, - string title, - CanvasPage canvasPage - ) - { - logger.Log($"creating new module item {title}"); - var url = $"courses/{canvasCourseId}/modules/{canvasModuleId}/items"; - var body = new - { - module_item = new - { - title, - type = "Page", - page_url = canvasPage.Url, - } - }; - var request = new RestRequest(url); - request.AddBody(body); - - var (newItem, _response) = await webRequestor.PostAsync(request); - if (newItem == null) - throw new Exception("something went wrong updating module item with string content id"); - } - - public async Task> GetEnrolledStudents( - ulong canvasCourseId - ) - { - logger.Log($"getting students for corse {canvasCourseId}"); - - var url = $"courses/{canvasCourseId}/enrollments?enrollment_type=student"; - var request = new RestRequest(url); - var (enrollments, _response) = await webRequestor.GetManyAsync(request); - if (enrollments == null) - throw new Exception($"something went wrong getting enrollments for {canvasCourseId}"); - - return enrollments; - } -} diff --git a/Management/Services/Canvas/CanvasServiceUtils.cs b/Management/Services/Canvas/CanvasServiceUtils.cs deleted file mode 100644 index 24ccdb0..0000000 --- a/Management/Services/Canvas/CanvasServiceUtils.cs +++ /dev/null @@ -1,62 +0,0 @@ -using RestSharp; - -namespace Management.Services.Canvas; - - -public class CanvasServiceUtils -{ - private const string BaseUrl = "https://snow.instructure.com/api/v1/"; - private readonly IWebRequestor webRequestor; - - public CanvasServiceUtils(IWebRequestor webRequestor) - { - this.webRequestor = webRequestor; - } - - internal async Task> PaginatedRequest(RestRequest request) - { - var requestCount = 1; - request.AddQueryParameter("per_page", "100"); - var (data, response) = await webRequestor.GetAsync(request); - - if (response.ErrorMessage?.Length > 0) - { - Console.WriteLine("error in response"); - Console.WriteLine(response.ErrorMessage); - throw new Exception("error in response"); - } - - var returnData = data != null ? new T[] { data } : new T[] { }; - var nextUrl = getNextUrl(response.Headers); - - while (nextUrl is not null) - { - requestCount += 1; - RestRequest nextRequest = new RestRequest(nextUrl); - var (nextData, nextResponse) = await webRequestor.GetAsync(nextRequest); - if (nextData is not null) - returnData = returnData.Append(nextData).ToArray(); - nextUrl = getNextUrl(nextResponse.Headers); - } - - if (requestCount > 1) - Console.WriteLine($"Requesting {typeof(T)} took {requestCount} requests"); - - return returnData; - } - - protected static string? getNextUrl(IEnumerable? headers) => - headers - ?.ToList() - .Find(h => h.Name == "Link") - ?.Value?.ToString() - ?.Split(",") - .Where(url => url.Contains("rel=\"next\"")) - .FirstOrDefault() - ?.Split(";") - .FirstOrDefault() - ?.TrimEnd('>') - .TrimStart('<') - .Replace(" ", "") - .Replace(BaseUrl, ""); -} diff --git a/Management/Services/Files/CourseDifferences.cs b/Management/Services/Files/CourseDifferences.cs deleted file mode 100644 index 6501a0d..0000000 --- a/Management/Services/Files/CourseDifferences.cs +++ /dev/null @@ -1,103 +0,0 @@ -using LocalModels; - -public static class CourseDifferences -{ - public static DeleteCourseChanges GetDeletedChanges(LocalCourse newCourse, LocalCourse oldCourse) - { - if (newCourse == oldCourse) - return new DeleteCourseChanges(); - var moduleNamesNoLongerReferenced = oldCourse.Modules - .Where(oldModule => - !newCourse.Modules.Any(newModule => newModule.Name == oldModule.Name) - ) - .Select(oldModule => oldModule.Name) - .ToList(); - - var modulesWithDeletions = oldCourse.Modules - .Where(oldModule => - !newCourse.Modules.Any(newModule => newModule.Equals(oldModule)) - ) - .Select(oldModule => - { - var newModule = newCourse.Modules.FirstOrDefault(m => m.Name == oldModule.Name); - if (newModule == null) - return oldModule; - - var unreferencedAssignments = oldModule.Assignments.Where(oldAssignment => - !newModule.Assignments.Any(newAssignment => newAssignment.Name == oldAssignment.Name) - ); - var unreferencedQuizzes = oldModule.Quizzes.Where(oldQuiz => - !newModule.Quizzes.Any(newQuiz => newQuiz.Name == oldQuiz.Name) - ); - var unreferencedPages = oldModule.Pages.Where(oldPage => - !newModule.Pages.Any(newPage => newPage.Name == oldPage.Name) - ); - - return oldModule with - { - Assignments = unreferencedAssignments, - Quizzes = unreferencedQuizzes, - Pages = unreferencedPages, - }; - }) - .ToList(); - - return new DeleteCourseChanges - { - NamesOfModulesToDeleteCompletely = moduleNamesNoLongerReferenced, - DeleteContentsOfModule = modulesWithDeletions, - }; - } - - public static NewCourseChanges GetNewChanges(LocalCourse newCourse, LocalCourse oldCourse) - { - if (newCourse == oldCourse) - return new NewCourseChanges(); - - var differentModules = newCourse.Modules - .Where(newModule => - !oldCourse.Modules.Any(oldModule => oldModule.Equals(newModule)) - ) - .Select(newModule => - { - var oldModule = oldCourse.Modules.FirstOrDefault(m => m.Name == newModule.Name); - if (oldModule == null) - return newModule; - - var newAssignments = newModule.Assignments.Where( - newAssignment => !oldModule.Assignments.Any(oldAssignment => newAssignment == oldAssignment) - ); - var newQuizzes = newModule.Quizzes.Where( - newQuiz => !oldModule.Quizzes.Any(oldQuiz => newQuiz == oldQuiz) - ); - var newPages = newModule.Pages.Where( - newPage => !oldModule.Pages.Any(oldPage => newPage == oldPage) - ); - return newModule with - { - Assignments = newAssignments, - Quizzes = newQuizzes, - Pages = newPages, - }; - }) - .ToArray(); - - return new NewCourseChanges - { - Settings = newCourse.Settings, - Modules = differentModules, - }; - } - -} - -public record NewCourseChanges -{ - public IEnumerable Modules { get; init; } = []; - public LocalCourseSettings? Settings { get; init; } -} -public record DeleteCourseChanges -{ - public IEnumerable NamesOfModulesToDeleteCompletely { get; init; } = []; - public IEnumerable DeleteContentsOfModule { get; init; } = []; -} \ No newline at end of file diff --git a/Management/Services/Files/FileConfiguration.cs b/Management/Services/Files/FileConfiguration.cs deleted file mode 100644 index ce7c8c1..0000000 --- a/Management/Services/Files/FileConfiguration.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Management.Services; -using Microsoft.Extensions.Configuration; - -public class FileConfiguration(IConfiguration config) -{ - public string GetBasePath() - { - string? storageDirectory = config["storageDirectory"]; - var basePath = storageDirectory ?? Path.GetFullPath("../storage"); - - if (!Directory.Exists(basePath)) - throw new Exception("storage folder not found"); - - return basePath; - - } -} diff --git a/Management/Services/Files/FileStorageService.cs b/Management/Services/Files/FileStorageService.cs deleted file mode 100644 index ac3d9a2..0000000 --- a/Management/Services/Files/FileStorageService.cs +++ /dev/null @@ -1,55 +0,0 @@ -using LocalModels; -using Management.Services; - -public class FileStorageService -{ - private readonly MyLogger logger; - private readonly CourseMarkdownLoader _courseMarkdownLoader; - private readonly MarkdownCourseSaver _saveMarkdownCourse; - private readonly ILogger _otherLogger; - private readonly string _basePath; - - public FileStorageService( - MyLogger logger, - CourseMarkdownLoader courseMarkdownLoader, - MarkdownCourseSaver saveMarkdownCourse, - ILogger otherLogger, - FileConfiguration fileConfig - ) - { - using var activity = DiagnosticsConfig.Source.StartActivity("loading storage directory"); - this.logger = logger; - _courseMarkdownLoader = courseMarkdownLoader; - _saveMarkdownCourse = saveMarkdownCourse; - _otherLogger = otherLogger; - _basePath = fileConfig.GetBasePath(); - - this.logger.Log("Using storage directory: " + _basePath); - } - public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse) - { - using var activity = DiagnosticsConfig.Source.StartActivity("Saving Course"); - activity?.AddTag("CourseName", course.Settings.Name); - await _saveMarkdownCourse.Save(course, previouslyStoredCourse); - } - - - public async Task> LoadSavedCourses() - { - - Console.WriteLine("loading pages from file system"); - return await _courseMarkdownLoader.LoadSavedCourses(); - } - - public async Task> GetEmptyDirectories() - { - if (!Directory.Exists(_basePath)) - throw new DirectoryNotFoundException($"Cannot get empty directories, {_basePath} does not exist"); - - return Directory - .GetDirectories(_basePath, "*") - .Where(dir => !Directory.EnumerateFileSystemEntries(dir).Any()) - .ToArray(); - - } -} diff --git a/Management/Services/Files/IFileStorageManager.cs b/Management/Services/Files/IFileStorageManager.cs deleted file mode 100644 index b45ffc5..0000000 --- a/Management/Services/Files/IFileStorageManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -using LocalModels; - -public interface IFileStorageManager -{ - Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse); - Task> LoadSavedCourses(); - Task> GetEmptyDirectories(); -} diff --git a/Management/Services/Files/LoadCourseFromFileException.cs b/Management/Services/Files/LoadCourseFromFileException.cs deleted file mode 100644 index bc4b6b9..0000000 --- a/Management/Services/Files/LoadCourseFromFileException.cs +++ /dev/null @@ -1,3 +0,0 @@ -public class LoadCourseFromFileException(string message) : Exception(message) -{ -} diff --git a/Management/Services/Files/LoadMarkdownCourse.cs b/Management/Services/Files/LoadMarkdownCourse.cs deleted file mode 100644 index 1250dae..0000000 --- a/Management/Services/Files/LoadMarkdownCourse.cs +++ /dev/null @@ -1,182 +0,0 @@ -using LocalModels; - -using Management.Services; - -public class CourseMarkdownLoader -{ - private readonly MyLogger logger; - private readonly string _basePath; - - public CourseMarkdownLoader(MyLogger logger, FileConfiguration fileConfig) - { - this.logger = logger; - _basePath = fileConfig.GetBasePath(); - } - - public async Task> LoadSavedCourses() - { - var courseDirectories = Directory.GetDirectories(_basePath); - - var courses = await Task.WhenAll( - courseDirectories - .Where(d => - { - var settingsPath = $"{d}/settings.yml"; - return File.Exists(settingsPath); - }) - .Select(async d => await LoadCourseByPath(d)) - .ToArray() - ); - return courses.OrderBy(c => c.Settings.Name); - } - - public async Task LoadCourseByPath(string courseDirectory) - { - if (!Directory.Exists(courseDirectory)) - { - var errorMessage = $"error loading course by name, could not find folder {courseDirectory}"; - logger.Log(errorMessage); - throw new LoadCourseFromFileException(errorMessage); - } - - try - { - - LocalCourseSettings settings = await loadCourseSettings(courseDirectory); - var modules = await loadCourseModules(courseDirectory); - - return new() - { - Settings = settings, - Modules = modules - }; - } - catch (Exception) - { - Console.WriteLine($"failed to load course at path: ${courseDirectory}"); - throw; - } - } - - private async Task loadCourseSettings(string courseDirectory) - { - var settingsPath = $"{courseDirectory}/settings.yml"; - if (!File.Exists(settingsPath)) - { - var errorMessage = $"error loading course by name, settings file {settingsPath}"; - logger.Log(errorMessage); - throw new LoadCourseFromFileException(errorMessage); - } - - var settingsString = await File.ReadAllTextAsync(settingsPath); - var settings = LocalCourseSettings.ParseYaml(settingsString); - - var folderName = Path.GetFileName(courseDirectory); - return settings with { Name = folderName }; - } - - private async Task> loadCourseModules(string courseDirectory) - { - var modulePaths = Directory.GetDirectories(courseDirectory); - var modules = await Task.WhenAll( - modulePaths - .Select(loadModuleFromPath) - .ToArray() - ); - return modules.OrderBy(m => m.Name); - } - - private async Task loadModuleFromPath(string modulePath) - { - var moduleName = Path.GetFileName(modulePath); - var assignments = await loadAssignmentsFromPath(modulePath); - var quizzes = await loadQuizzesFromPath(modulePath); - var pages = await loadModulePagesFromPath(modulePath); - - return new LocalModule() - { - Name = moduleName, - Assignments = assignments, - Quizzes = quizzes, - Pages = pages, - }; - } - - private async Task> loadAssignmentsFromPath(string modulePath) - { - var assignmentsPath = $"{modulePath}/assignments"; - if (!Directory.Exists(assignmentsPath)) - { - logger.Log($"error loading course by name, assignments folder does not exist in {modulePath}"); - Directory.CreateDirectory(assignmentsPath); - } - - var assignmentFiles = Directory.GetFiles(assignmentsPath); - var assignmentPromises = assignmentFiles - .Select(async filePath => - { - var rawFile = (await File.ReadAllTextAsync(filePath)).Replace("\r\n", "\n"); - try - { - return LocalAssignment.ParseMarkdown(rawFile); - } - catch - { - Console.WriteLine($"error loading assignment at path {filePath}"); - throw; - } - }) - .ToArray(); - return await Task.WhenAll(assignmentPromises); - } - - private async Task> loadQuizzesFromPath(string modulePath) - { - var quizzesPath = $"{modulePath}/quizzes"; - if (!Directory.Exists(quizzesPath)) - { - logger.Log($"quizzes folder does not exist in {modulePath}, creating now"); - Directory.CreateDirectory(quizzesPath); - } - - var quizFiles = Directory.GetFiles(quizzesPath); - var quizPromises = quizFiles - .Select(async path => - { - var rawQuiz = (await File.ReadAllTextAsync(path)).Replace("\r\n", "\n"); - return LocalQuiz.ParseMarkdown(rawQuiz); - }) - .ToArray(); - - return await Task.WhenAll(quizPromises); - } - private async Task> loadModulePagesFromPath(string modulePath) - { - var pagesPath = $"{modulePath}/pages"; - if (!Directory.Exists(pagesPath)) - { - logger.Log($"pages folder does not exist in {modulePath}, creating now"); - Directory.CreateDirectory(pagesPath); - } - - var pageFiles = Directory.GetFiles(pagesPath); - var pagePromises = pageFiles - .Select(async path => - { - - var rawPage = (await File.ReadAllTextAsync(path)).Replace("\r\n", "\n"); - try - { - return LocalCoursePage.ParseMarkdown(rawPage); - } - catch - { - Console.WriteLine($"error loading page at path {path}"); - throw; - } - }) - .ToArray(); - - return await Task.WhenAll(pagePromises); - } -} diff --git a/Management/Services/Files/SaveMarkdownCourse.cs b/Management/Services/Files/SaveMarkdownCourse.cs deleted file mode 100644 index 930c986..0000000 --- a/Management/Services/Files/SaveMarkdownCourse.cs +++ /dev/null @@ -1,194 +0,0 @@ -using System.Threading.Tasks.Sources; -using LocalModels; -namespace Management.Services; - -public class MarkdownCourseSaver(MyLogger logger, FileConfiguration fileConfig) -{ - private readonly MyLogger _logger = logger; - private readonly string _basePath = fileConfig.GetBasePath(); - - public async Task Save(LocalCourse course, LocalCourse? previouslyStoredCourse) - { - var courseDirectory = $"{_basePath}/{course.Settings.Name}"; - if (!Directory.Exists(courseDirectory)) - Directory.CreateDirectory(courseDirectory); - await saveSettings(course, courseDirectory); - await saveModules(course, courseDirectory, previouslyStoredCourse); - } - - private async Task saveModules(LocalCourse course, string courseDirectory, LocalCourse? previouslyStoredCourse) - { - foreach (var module in course.Modules) - { - var moduleDirectory = courseDirectory + "/" + module.Name; - if (!Directory.Exists(moduleDirectory)) - Directory.CreateDirectory(moduleDirectory); - - await saveQuizzes(course, module, previouslyStoredCourse); - await saveAssignments(course, module, previouslyStoredCourse); - await savePages(course, module, previouslyStoredCourse); - } - - var moduleNames = course.Modules.Select(m => m.Name); - foreach (var moduleDirectoryPath in Directory.EnumerateDirectories(courseDirectory)) - { - var directoryName = Path.GetFileName(moduleDirectoryPath); - if (!moduleNames.Contains(directoryName)) - { - Console.WriteLine($"deleting extra module directory, it was probably renamed {moduleDirectoryPath}"); - Directory.Delete(moduleDirectoryPath, true); - } - } - } - - private static async Task saveSettings(LocalCourse course, string courseDirectory) - { - var settingsFilePath = courseDirectory + "/settings.yml"; - var settingsYaml = course.Settings.ToYaml(); - await File.WriteAllTextAsync(settingsFilePath, settingsYaml); - } - - private async Task saveQuizzes(LocalCourse course, LocalModule module, LocalCourse? previouslyStoredCourse) - { - var quizzesDirectory = $"{_basePath}/{course.Settings.Name}/{module.Name}/quizzes"; - if (!Directory.Exists(quizzesDirectory)) - Directory.CreateDirectory(quizzesDirectory); - - - foreach (var quiz in module.Quizzes) - { - - var previousModule = previouslyStoredCourse?.Modules.FirstOrDefault(m => m.Name == module.Name); - var previousQuiz = previousModule?.Quizzes.FirstOrDefault(q => q == quiz); - - if (previousQuiz == null) - { - var markdownPath = quizzesDirectory + "/" + quiz.Name + ".md"; ; - var quizMarkdown = quiz.ToMarkdown(); - _logger.Log("saving quiz " + markdownPath); - await File.WriteAllTextAsync(markdownPath, quizMarkdown); - } - } - removeOldQuizzes(quizzesDirectory, module); - } - - private void removeOldQuizzes(string path, LocalModule module) - { - var existingFiles = Directory.EnumerateFiles(path); - - var filesToDelete = existingFiles.Where((f) => - { - foreach (var quiz in module.Quizzes) - { - var markdownPath = path + "/" + quiz.Name + ".md"; - if (f == markdownPath) - return false; - } - return true; - }); - - foreach (var file in filesToDelete) - { - _logger.Log($"removing old quiz, it has probably been renamed {file}"); - File.Delete(file); - } - - } - - private async Task saveAssignments(LocalCourse course, LocalModule module, LocalCourse? previouslyStoredCourse) - { - var assignmentsDirectory = $"{_basePath}/{course.Settings.Name}/{module.Name}/assignments"; - if (!Directory.Exists(assignmentsDirectory)) - Directory.CreateDirectory(assignmentsDirectory); - - foreach (var assignment in module.Assignments) - { - - var previousModule = previouslyStoredCourse?.Modules.FirstOrDefault(m => m.Name == module.Name); - var previousAssignment = previousModule?.Assignments.FirstOrDefault(a => a == assignment); - - if (previousAssignment == null) - { - var assignmentMarkdown = assignment.ToMarkdown(); - - var filePath = assignmentsDirectory + "/" + assignment.Name + ".md"; - _logger.Log("saving assignment " + filePath); - await File.WriteAllTextAsync(filePath, assignmentMarkdown); - } - } - removeOldAssignments(assignmentsDirectory, module); - } - - private void removeOldAssignments(string path, LocalModule module) - { - var existingFiles = Directory.EnumerateFiles(path); - - var filesToDelete = existingFiles.Where((f) => - { - foreach (var assignment in module.Assignments) - { - var markdownPath = path + "/" + assignment.Name + ".md"; - if (f == markdownPath) - return false; - } - return true; - }); - - foreach (var file in filesToDelete) - { - _logger.Log($"removing old assignment, it has probably been renamed {file}"); - File.Delete(file); - } - } - - private async Task savePages(LocalCourse course, LocalModule module, LocalCourse? previouslyStoredCourse) - { - var pagesDirectory = $"{_basePath}/{course.Settings.Name}/{module.Name}/pages"; - if (!Directory.Exists(pagesDirectory)) - Directory.CreateDirectory(pagesDirectory); - - - foreach (var page in module.Pages) - { - var previousModule = previouslyStoredCourse?.Modules.FirstOrDefault(m => m.Name == module.Name); - var previousPage = previousModule?.Pages.FirstOrDefault(a => a == page); - - if (previousPage == null) - { - var assignmentMarkdown = page.ToMarkdown(); - - var filePath = pagesDirectory + "/" + page.Name + ".md"; - using var activity = DiagnosticsConfig.Source.StartActivity("saving page in module"); - activity?.AddTag("PageName", page.Name); - _logger.Log("saving page " + filePath); - await File.WriteAllTextAsync(filePath, assignmentMarkdown); - } - } - removeOldPages(pagesDirectory, module); - } - - private void removeOldPages(string path, LocalModule module) - { - var existingFiles = Directory.EnumerateFiles(path); - - var filesToDelete = existingFiles.Where((f) => - { - foreach (var page in module.Pages) - { - var markdownPath = path + "/" + page.Name + ".md"; - if (f == markdownPath) - return false; - } - return true; - }); - - foreach (var file in filesToDelete) - { - _logger.Log($"removing old assignment, it has probably been renamed {file}"); - using var activity = DiagnosticsConfig.Source.StartActivity("removing untracked page from module"); - activity?.AddTag("FileName", file); - File.Delete(file); - } - } - -} diff --git a/Management/Services/IWebRequestor.cs b/Management/Services/IWebRequestor.cs deleted file mode 100644 index e189bd1..0000000 --- a/Management/Services/IWebRequestor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using RestSharp; - -public interface IWebRequestor -{ - Task<(T[]?, RestResponse)> GetManyAsync(RestRequest request); - Task<(T?, RestResponse)> GetAsync(RestRequest request); - Task PostAsync(RestRequest request); - Task<(T?, RestResponse)> PostAsync(RestRequest request); - Task PutAsync(RestRequest request); - Task<(T?, RestResponse)> PutAsync(RestRequest request); - Task DeleteAsync(RestRequest request); -} diff --git a/Management/Services/MarkdownService.cs b/Management/Services/MarkdownService.cs deleted file mode 100644 index de7e30e..0000000 --- a/Management/Services/MarkdownService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Markdig; - -public static class MarkdownService -{ - public static string Render(string incomingMarkdown) - { - var pipeline = new MarkdownPipelineBuilder() - .UseAdvancedExtensions() - .Build(); - return Markdown.ToHtml(incomingMarkdown, pipeline); - } -} diff --git a/Management/Services/MyLogger.cs b/Management/Services/MyLogger.cs deleted file mode 100644 index e73a0a1..0000000 --- a/Management/Services/MyLogger.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Management.Services; - - -public class MyLogger -{ - private readonly ILogger _baseLogger; - - public MyLogger(ILogger baseLogger) - { - this._baseLogger = baseLogger; - } - - public void Log( - string message, - // LogLevel logLevel = LogLevel.Information, - [CallerMemberName] string memberName = "" - ) - { - var finalMessage = $"[{typeof(T)}.{memberName}] {message}"; - - _baseLogger.LogInformation(finalMessage); - Console.WriteLine(finalMessage); - } - - public void Trace( - string message, - LogLevel logLevel = LogLevel.Trace, - [CallerMemberName] string memberName = "" - ) - { - var finalMessage = $"[{typeof(T)}.{memberName}] {message}"; - - _baseLogger.Log(logLevel, finalMessage); - // Console.WriteLine(finalMessage); - } - public void Error( - string message, - LogLevel logLevel = LogLevel.Error, - [CallerMemberName] string memberName = "" - ) - { - var finalMessage = $"ERROR: [{typeof(T)}.{memberName}] {message}"; - - _baseLogger.Log(logLevel, finalMessage); - Console.WriteLine(finalMessage); - } -} diff --git a/Management/Services/WebRequestor.cs b/Management/Services/WebRequestor.cs deleted file mode 100644 index 02b1a8a..0000000 --- a/Management/Services/WebRequestor.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System.Net; -using Microsoft.Extensions.Configuration; -using RestSharp; - -public class WebRequestor : IWebRequestor -{ - private string BaseUrl = ""; - private string token; - private RestClient client; - private readonly IConfiguration _config; - private readonly ILogger logger; - private static int rateLimitRetryCount = 6; - private static int rateLimitSleepInterval = 1000; - - public WebRequestor(IConfiguration config, ILogger logger) - { - _config = config; - this.logger = logger; - token = - _config["CANVAS_TOKEN"] - ?? throw new Exception("CANVAS_TOKEN not in environment"); - BaseUrl = _config["CANVAS_URL"] + "/api/v1/"; - client = new RestClient(BaseUrl); - client.AddDefaultHeader("Authorization", $"Bearer {token}"); - } - - public async Task<(T[]?, RestResponse)> GetManyAsync(RestRequest request) - { - var response = await client.ExecuteGetAsync(request); - return (deserialize(response), response); - } - - public async Task<(T?, RestResponse)> GetAsync(RestRequest request) - { - var response = await client.ExecuteGetAsync(request); - return (deserialize(response), response); - } - - public async Task PostAsync(RestRequest request) => await rateLimitAwarePostAsync(request, 0); - - - private async Task rateLimitAwarePostAsync(RestRequest request, int retryCount = 0) - { - request.AddHeader("Content-Type", "application/json"); - - var response = await client.ExecutePostAsync(request); - - if (isRateLimited(response)) - { - if (retryCount < rateLimitRetryCount) - { - logger.LogInformation($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying"); - Console.WriteLine($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying"); - Thread.Sleep(rateLimitSleepInterval); - return await rateLimitAwarePostAsync(request, retryCount + 1); - } - } - - if (!response.IsSuccessful) - { - logger.LogError($"Error with response, response content: {response.Content}"); - throw new Exception($"error post response, retrycount: {retryCount}, ratelimited: {isRateLimited(response)}, code: {response.StatusCode}, response content: {response.Content}"); - } - return response; - } - - private static bool isRateLimited(RestResponse response) - { - if (response.Content == null) - return false; - return response.StatusCode == HttpStatusCode.Forbidden - && response.Content.Contains("403 Forbidden (Rate Limit Exceeded)"); - } - - public async Task<(T?, RestResponse)> PostAsync(RestRequest request) - { - var response = await PostAsync(request); - return (deserialize(response), response); - } - - public async Task PutAsync(RestRequest request) - { - request.AddHeader("Content-Type", "application/json"); - var response = await client.ExecutePutAsync(request); - // if (!response.IsSuccessful) - // { - // Console.WriteLine(response.Content); - // Console.WriteLine(response.ResponseUri); - // Console.WriteLine("error with response"); - // throw new Exception("error with response"); - // } - return response; - } - - public async Task<(T?, RestResponse)> PutAsync(RestRequest request) - { - request.AddHeader("Content-Type", "application/json"); - var response = await client.ExecutePutAsync(request); - return (deserialize(response), response); - } - - public Task DeleteAsync(RestRequest request) => recursiveDeleteAsync(request, 0); - private async Task recursiveDeleteAsync(RestRequest request, int retryCount) - { - try - { - var response = await client.DeleteAsync(request); - if (isRateLimited(response)) - Console.WriteLine("after delete response in rate limited"); - return response; - } - catch (HttpRequestException e) - { - if (e.StatusCode == HttpStatusCode.Forbidden) // && response.Content == "403 Forbidden (Rate Limit Exceeded)" - { - if (retryCount < rateLimitRetryCount) - { - logger.LogInformation($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying"); - Console.WriteLine($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying"); - Thread.Sleep(rateLimitSleepInterval); - return await recursiveDeleteAsync(request, retryCount + 1); - } - else - { - logger.LogInformation($"hit rate limit in delete, {rateLimitRetryCount} retries did not fix it"); - } - } - throw; - } - } - - private static T? deserialize(RestResponse response) - { - // using var activity = DiagnosticsConfig.Source.StartActivity("deserializing response"); - // activity?.AddTag("url", response.ResponseUri); - - if (!response.IsSuccessful) - { - Console.WriteLine(response.Content); - Console.WriteLine(response.ResponseUri); - Console.WriteLine(response.ErrorMessage); - Console.WriteLine("error with response"); - // Console.WriteLine(JsonSerializer.Serialize(response)); - Console.WriteLine(JsonSerializer.Serialize(response.Request?.Parameters)); - throw new Exception($"error with response to {response.ResponseUri} {response.StatusCode}"); - } - try - { - var data = JsonSerializer.Deserialize(response.Content!); - - if (data == null) - { - Console.WriteLine(response.Content); - Console.WriteLine(response.ResponseUri); - Console.WriteLine("could not parse response, got empty object"); - } - return data; - } - catch (NotSupportedException) - { - Console.WriteLine(response.Content); - throw; - } - catch (JsonException) - { - Console.WriteLine(response.ResponseUri); - Console.WriteLine(response.Content); - Console.WriteLine($"An error occurred during deserialization"); - throw; - } - } - -} diff --git a/build.sh b/build.sh index d59e6b6..d271be9 100755 --- a/build.sh +++ b/build.sh @@ -1,25 +1,19 @@ #!/bin/bash - -MAJOR_VERSION="1" -MINOR_VERSION="5" +MAJOR_VERSION="2" +MINOR_VERSION="3" VERSION="$MAJOR_VERSION.$MINOR_VERSION" -dotnet publish Management.Web/ \ - --os linux \ - --arch x64 \ - /t:PublishContainer \ - -c Release \ - -p ContainerImageTags="\"$MAJOR_VERSION;$VERSION;latest\"" \ - -p ContainerRepository="canvas_management" +docker build -t canvas_management:$VERSION . echo "to push run: " echo "" echo "docker image tag canvas_management:$VERSION alexmickelson/canvas_management:$VERSION" -echo "docker image tag canvas_management:$MAJOR_VERSION alexmickelson/canvas_management:$MAJOR_VERSION" +echo "docker image tag canvas_management:$VERSION alexmickelson/canvas_management:$MAJOR_VERSION" echo "docker image tag canvas_management:latest alexmickelson/canvas_management:latest" echo "docker push alexmickelson/canvas_management:$VERSION" echo "docker push alexmickelson/canvas_management:$MAJOR_VERSION" echo "docker push alexmickelson/canvas_management:latest" + diff --git a/canvasManagement.sln b/canvasManagement.sln deleted file mode 100644 index 9f4e832..0000000 --- a/canvasManagement.sln +++ /dev/null @@ -1,34 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Management", "Management\Management.csproj", "{FC5771C2-8752-4871-B0CA-A9C9AC5E4CA5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Management.Test", "Management.Test\Management.Test.csproj", "{1C1D51D9-FE2F-47E9-B60F-BAAFBA0808C0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Management.Web", "Management.Web\Management.Web.csproj", "{8828A70D-8DAD-4FD0-870A-6437595BCE8F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FC5771C2-8752-4871-B0CA-A9C9AC5E4CA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC5771C2-8752-4871-B0CA-A9C9AC5E4CA5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC5771C2-8752-4871-B0CA-A9C9AC5E4CA5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC5771C2-8752-4871-B0CA-A9C9AC5E4CA5}.Release|Any CPU.Build.0 = Release|Any CPU - {1C1D51D9-FE2F-47E9-B60F-BAAFBA0808C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C1D51D9-FE2F-47E9-B60F-BAAFBA0808C0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C1D51D9-FE2F-47E9-B60F-BAAFBA0808C0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C1D51D9-FE2F-47E9-B60F-BAAFBA0808C0}.Release|Any CPU.Build.0 = Release|Any CPU - {8828A70D-8DAD-4FD0-870A-6437595BCE8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8828A70D-8DAD-4FD0-870A-6437595BCE8F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8828A70D-8DAD-4FD0-870A-6437595BCE8F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8828A70D-8DAD-4FD0-870A-6437595BCE8F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/nextjs/devtunnel.sh b/devtunnel.sh similarity index 100% rename from nextjs/devtunnel.sh rename to devtunnel.sh diff --git a/docker-compose.yml b/docker-compose.yml index b0ec363..0be2cfe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,41 +1,45 @@ services: canvas_manager: - image: alexmickelson/canvas_management:1 + image: alexmickelson/canvas_management:2.3 user: "1000:1000" + container_name: canvas-manager-2 ports: - - 8080:8080 + - 3000:3000 env_file: - .env environment: - storageDirectory=/app/storage - TZ=America/Denver + # - FILE_POLLING=true volumes: - ~/projects/faculty/1430/2024-fall-alex/modules:/app/storage/UX - ~/projects/faculty/4850_AdvancedFE/2024-fall-alex/modules:/app/storage/advanced_frontend - ~/projects/faculty/1810/2024-fall-alex/modules:/app/storage/intro_to_web - ~/projects/faculty/1420/2024-fall/Modules:/app/storage/1420 - ~/projects/faculty/1425/2024-fall/Modules:/app/storage/1425 + - ~/projects/faculty/3840_Telemetry/2024Spring_alex/modules:/app/storage/spring_2024_telemetry + - ~/projects/faculty/3840_Telemetry/2025_spring_alex/modules:/app/storage/spring_2025_telemetry + - ~/projects/faculty/4620_Distributed/2025Spring/modules:/app/storage/distributed + + # https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/ + # https://github.com/jonas-merkle/container-cloudflare-tunnel + cloudflare-tunnel: + image: cloudflare/cloudflared + container_name: cloudflare-tunnel + restart: unless-stopped + env_file: + - .env + command: tunnel run + volumes: + - /etc/localtime:/etc/localtime:ro + environment: + - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} + healthcheck: + test: ["CMD", "cloudflared", "--version"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s - - # collector: - # image: otel/opentelemetry-collector-contrib - # volumes: - # - ./canvas-development/otel-collector-config.yml:/etc/otelcol-contrib/config.yaml - # ports: - # - 1888:1888 # pprof extension - # - 8888:8888 # Prometheus metrics exposed by the Collector - # - 8889:8889 # Prometheus exporter metrics - # - 13133:13133 # health_check extension - # - 4317:4317 # OTLP gRPC receiver - # - 4318:4318 # OTLP http receiver - # - 55679:55679 # zpages extension - - # zipkin: - # image: ghcr.io/openzipkin/zipkin-slim - # # Environment settings are defined here https://github.com/openzipkin/zipkin/blob/master/zipkin-server/README.md#environment-variables - # environment: - # - STORAGE_TYPE=mem - # ports: - # - 9411:9411 - # # command: --logging.level.zipkin2=DEBUG + # https://ngrok.com/docs/using-ngrok-with/docker/ diff --git a/nextjs/next.config.mjs b/next.config.mjs similarity index 100% rename from nextjs/next.config.mjs rename to next.config.mjs diff --git a/nextjs/.env.test b/nextjs/.env.test deleted file mode 100644 index 72a67cb..0000000 --- a/nextjs/.env.test +++ /dev/null @@ -1 +0,0 @@ -STORAGE_DIRECTORY="./temp/canvasManagerStorage" \ No newline at end of file diff --git a/nextjs/.gitignore b/nextjs/.gitignore deleted file mode 100644 index d34522c..0000000 --- a/nextjs/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -.pnpm-store/ -tmp.json - - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - - -storage/ -temp/ \ No newline at end of file diff --git a/nextjs/README.md b/nextjs/README.md deleted file mode 100644 index 92f232d..0000000 --- a/nextjs/README.md +++ /dev/null @@ -1,17 +0,0 @@ - - -- [ ] check out trpc - - - - - - - -lecture link prefectch false - -assignment group weight kicking out user -assignment name kicking out on debounce - - -success message disappeared -- probably the server updating the settings file - diff --git a/nextjs/build.sh b/nextjs/build.sh deleted file mode 100755 index d271be9..0000000 --- a/nextjs/build.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -MAJOR_VERSION="2" -MINOR_VERSION="3" -VERSION="$MAJOR_VERSION.$MINOR_VERSION" - -docker build -t canvas_management:$VERSION . - - - -echo "to push run: " -echo "" -echo "docker image tag canvas_management:$VERSION alexmickelson/canvas_management:$VERSION" -echo "docker image tag canvas_management:$VERSION alexmickelson/canvas_management:$MAJOR_VERSION" -echo "docker image tag canvas_management:latest alexmickelson/canvas_management:latest" -echo "docker push alexmickelson/canvas_management:$VERSION" -echo "docker push alexmickelson/canvas_management:$MAJOR_VERSION" -echo "docker push alexmickelson/canvas_management:latest" - diff --git a/nextjs/docker-compose.yml b/nextjs/docker-compose.yml deleted file mode 100644 index 0be2cfe..0000000 --- a/nextjs/docker-compose.yml +++ /dev/null @@ -1,45 +0,0 @@ -services: - canvas_manager: - image: alexmickelson/canvas_management:2.3 - user: "1000:1000" - container_name: canvas-manager-2 - ports: - - 3000:3000 - env_file: - - .env - environment: - - storageDirectory=/app/storage - - TZ=America/Denver - # - FILE_POLLING=true - volumes: - - ~/projects/faculty/1430/2024-fall-alex/modules:/app/storage/UX - - ~/projects/faculty/4850_AdvancedFE/2024-fall-alex/modules:/app/storage/advanced_frontend - - ~/projects/faculty/1810/2024-fall-alex/modules:/app/storage/intro_to_web - - ~/projects/faculty/1420/2024-fall/Modules:/app/storage/1420 - - ~/projects/faculty/1425/2024-fall/Modules:/app/storage/1425 - - ~/projects/faculty/3840_Telemetry/2024Spring_alex/modules:/app/storage/spring_2024_telemetry - - ~/projects/faculty/3840_Telemetry/2025_spring_alex/modules:/app/storage/spring_2025_telemetry - - ~/projects/faculty/4620_Distributed/2025Spring/modules:/app/storage/distributed - - # https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/ - # https://github.com/jonas-merkle/container-cloudflare-tunnel - cloudflare-tunnel: - image: cloudflare/cloudflared - container_name: cloudflare-tunnel - restart: unless-stopped - env_file: - - .env - command: tunnel run - volumes: - - /etc/localtime:/etc/localtime:ro - environment: - - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} - healthcheck: - test: ["CMD", "cloudflared", "--version"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 10s - - - # https://ngrok.com/docs/using-ngrok-with/docker/ diff --git a/nextjs/versions b/nextjs/versions deleted file mode 100644 index e69de29..0000000 diff --git a/nextjs/package.json b/package.json similarity index 100% rename from nextjs/package.json rename to package.json diff --git a/nextjs/pnpm-lock.yaml b/pnpm-lock.yaml similarity index 100% rename from nextjs/pnpm-lock.yaml rename to pnpm-lock.yaml diff --git a/nextjs/postcss.config.mjs b/postcss.config.mjs similarity index 100% rename from nextjs/postcss.config.mjs rename to postcss.config.mjs diff --git a/nextjs/public/next.svg b/public/next.svg similarity index 100% rename from nextjs/public/next.svg rename to public/next.svg diff --git a/nextjs/public/vercel.svg b/public/vercel.svg similarity index 100% rename from nextjs/public/vercel.svg rename to public/vercel.svg diff --git a/nextjs/run.sh b/run.sh similarity index 100% rename from nextjs/run.sh rename to run.sh diff --git a/nextjs/src/app/CourseList.tsx b/src/app/CourseList.tsx similarity index 100% rename from nextjs/src/app/CourseList.tsx rename to src/app/CourseList.tsx diff --git a/nextjs/src/app/MyToaster.tsx b/src/app/MyToaster.tsx similarity index 100% rename from nextjs/src/app/MyToaster.tsx rename to src/app/MyToaster.tsx diff --git a/nextjs/src/app/api/canvas/[...rest]/route.ts b/src/app/api/canvas/[...rest]/route.ts similarity index 100% rename from nextjs/src/app/api/canvas/[...rest]/route.ts rename to src/app/api/canvas/[...rest]/route.ts diff --git a/nextjs/src/app/api/trpc/[trpc]/route.ts b/src/app/api/trpc/[trpc]/route.ts similarity index 100% rename from nextjs/src/app/api/trpc/[trpc]/route.ts rename to src/app/api/trpc/[trpc]/route.ts diff --git a/nextjs/src/app/course/[courseName]/CourseNavigation.tsx b/src/app/course/[courseName]/CourseNavigation.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/CourseNavigation.tsx rename to src/app/course/[courseName]/CourseNavigation.tsx diff --git a/nextjs/src/app/course/[courseName]/CourseSettingsLink.tsx b/src/app/course/[courseName]/CourseSettingsLink.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/CourseSettingsLink.tsx rename to src/app/course/[courseName]/CourseSettingsLink.tsx diff --git a/nextjs/src/app/course/[courseName]/CourseTitle.tsx b/src/app/course/[courseName]/CourseTitle.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/CourseTitle.tsx rename to src/app/course/[courseName]/CourseTitle.tsx diff --git a/nextjs/src/app/course/[courseName]/calendar/CalendarMonth.tsx b/src/app/course/[courseName]/calendar/CalendarMonth.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/CalendarMonth.tsx rename to src/app/course/[courseName]/calendar/CalendarMonth.tsx diff --git a/nextjs/src/app/course/[courseName]/calendar/CalendarWeek.tsx b/src/app/course/[courseName]/calendar/CalendarWeek.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/CalendarWeek.tsx rename to src/app/course/[courseName]/calendar/CalendarWeek.tsx diff --git a/nextjs/src/app/course/[courseName]/calendar/CourseCalendar.tsx b/src/app/course/[courseName]/calendar/CourseCalendar.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/CourseCalendar.tsx rename to src/app/course/[courseName]/calendar/CourseCalendar.tsx diff --git a/nextjs/src/app/course/[courseName]/calendar/calendarMonthUtils.ts b/src/app/course/[courseName]/calendar/calendarMonthUtils.ts similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/calendarMonthUtils.ts rename to src/app/course/[courseName]/calendar/calendarMonthUtils.ts diff --git a/nextjs/src/app/course/[courseName]/calendar/calendarUtils.test.ts b/src/app/course/[courseName]/calendar/calendarUtils.test.ts similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/calendarUtils.test.ts rename to src/app/course/[courseName]/calendar/calendarUtils.test.ts diff --git a/nextjs/src/app/course/[courseName]/calendar/calendarUtils.tsx b/src/app/course/[courseName]/calendar/calendarUtils.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/calendarUtils.tsx rename to src/app/course/[courseName]/calendar/calendarUtils.tsx diff --git a/nextjs/src/app/course/[courseName]/calendar/day/Day.tsx b/src/app/course/[courseName]/calendar/day/Day.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/day/Day.tsx rename to src/app/course/[courseName]/calendar/day/Day.tsx diff --git a/nextjs/src/app/course/[courseName]/calendar/day/DayTitle.tsx b/src/app/course/[courseName]/calendar/day/DayTitle.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/day/DayTitle.tsx rename to src/app/course/[courseName]/calendar/day/DayTitle.tsx diff --git a/nextjs/src/app/course/[courseName]/calendar/day/ItemInDay.tsx b/src/app/course/[courseName]/calendar/day/ItemInDay.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/day/ItemInDay.tsx rename to src/app/course/[courseName]/calendar/day/ItemInDay.tsx diff --git a/nextjs/src/app/course/[courseName]/calendar/day/getStatus.tsx b/src/app/course/[courseName]/calendar/day/getStatus.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/day/getStatus.tsx rename to src/app/course/[courseName]/calendar/day/getStatus.tsx diff --git a/nextjs/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx b/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx rename to src/app/course/[courseName]/calendar/day/useTodaysItems.tsx diff --git a/nextjs/src/app/course/[courseName]/context/CalendarItemsContextProvider.tsx b/src/app/course/[courseName]/context/CalendarItemsContextProvider.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/context/CalendarItemsContextProvider.tsx rename to src/app/course/[courseName]/context/CalendarItemsContextProvider.tsx diff --git a/nextjs/src/app/course/[courseName]/context/CourseContextProvider.tsx b/src/app/course/[courseName]/context/CourseContextProvider.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/context/CourseContextProvider.tsx rename to src/app/course/[courseName]/context/CourseContextProvider.tsx diff --git a/nextjs/src/app/course/[courseName]/context/LectureReplaceModal.tsx b/src/app/course/[courseName]/context/LectureReplaceModal.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/context/LectureReplaceModal.tsx rename to src/app/course/[courseName]/context/LectureReplaceModal.tsx diff --git a/nextjs/src/app/course/[courseName]/context/calendarItemsContext.ts b/src/app/course/[courseName]/context/calendarItemsContext.ts similarity index 100% rename from nextjs/src/app/course/[courseName]/context/calendarItemsContext.ts rename to src/app/course/[courseName]/context/calendarItemsContext.ts diff --git a/nextjs/src/app/course/[courseName]/context/courseContext.ts b/src/app/course/[courseName]/context/courseContext.ts similarity index 100% rename from nextjs/src/app/course/[courseName]/context/courseContext.ts rename to src/app/course/[courseName]/context/courseContext.ts diff --git a/nextjs/src/app/course/[courseName]/context/drag/DraggingContextProvider.tsx b/src/app/course/[courseName]/context/drag/DraggingContextProvider.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/context/drag/DraggingContextProvider.tsx rename to src/app/course/[courseName]/context/drag/DraggingContextProvider.tsx diff --git a/nextjs/src/app/course/[courseName]/context/drag/dragStyleContext.tsx b/src/app/course/[courseName]/context/drag/dragStyleContext.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/context/drag/dragStyleContext.tsx rename to src/app/course/[courseName]/context/drag/dragStyleContext.tsx diff --git a/nextjs/src/app/course/[courseName]/context/drag/draggingContext.tsx b/src/app/course/[courseName]/context/drag/draggingContext.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/context/drag/draggingContext.tsx rename to src/app/course/[courseName]/context/drag/draggingContext.tsx diff --git a/nextjs/src/app/course/[courseName]/context/drag/getNewLockDate.ts b/src/app/course/[courseName]/context/drag/getNewLockDate.ts similarity index 100% rename from nextjs/src/app/course/[courseName]/context/drag/getNewLockDate.ts rename to src/app/course/[courseName]/context/drag/getNewLockDate.ts diff --git a/nextjs/src/app/course/[courseName]/context/drag/useItemDropOnDay.ts b/src/app/course/[courseName]/context/drag/useItemDropOnDay.ts similarity index 100% rename from nextjs/src/app/course/[courseName]/context/drag/useItemDropOnDay.ts rename to src/app/course/[courseName]/context/drag/useItemDropOnDay.ts diff --git a/nextjs/src/app/course/[courseName]/context/drag/useItemDropOnModule.ts b/src/app/course/[courseName]/context/drag/useItemDropOnModule.ts similarity index 100% rename from nextjs/src/app/course/[courseName]/context/drag/useItemDropOnModule.ts rename to src/app/course/[courseName]/context/drag/useItemDropOnModule.ts diff --git a/nextjs/src/app/course/[courseName]/layout.tsx b/src/app/course/[courseName]/layout.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/layout.tsx rename to src/app/course/[courseName]/layout.tsx diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx b/src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx rename to src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/EditLectureTitle.tsx b/src/app/course/[courseName]/lecture/[lectureDay]/EditLectureTitle.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/lecture/[lectureDay]/EditLectureTitle.tsx rename to src/app/course/[courseName]/lecture/[lectureDay]/EditLectureTitle.tsx diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/LectureButtons.tsx b/src/app/course/[courseName]/lecture/[lectureDay]/LectureButtons.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/lecture/[lectureDay]/LectureButtons.tsx rename to src/app/course/[courseName]/lecture/[lectureDay]/LectureButtons.tsx diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/LecturePreview.tsx b/src/app/course/[courseName]/lecture/[lectureDay]/LecturePreview.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/lecture/[lectureDay]/LecturePreview.tsx rename to src/app/course/[courseName]/lecture/[lectureDay]/LecturePreview.tsx diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/layout.tsx b/src/app/course/[courseName]/lecture/[lectureDay]/layout.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/lecture/[lectureDay]/layout.tsx rename to src/app/course/[courseName]/lecture/[lectureDay]/layout.tsx diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/page.tsx b/src/app/course/[courseName]/lecture/[lectureDay]/page.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/lecture/[lectureDay]/page.tsx rename to src/app/course/[courseName]/lecture/[lectureDay]/page.tsx diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/preview/LecturePreviewPage.tsx b/src/app/course/[courseName]/lecture/[lectureDay]/preview/LecturePreviewPage.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/lecture/[lectureDay]/preview/LecturePreviewPage.tsx rename to src/app/course/[courseName]/lecture/[lectureDay]/preview/LecturePreviewPage.tsx diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/preview/page.tsx b/src/app/course/[courseName]/lecture/[lectureDay]/preview/page.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/lecture/[lectureDay]/preview/page.tsx rename to src/app/course/[courseName]/lecture/[lectureDay]/preview/page.tsx diff --git a/nextjs/src/app/course/[courseName]/loading.tsx b/src/app/course/[courseName]/loading.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/loading.tsx rename to src/app/course/[courseName]/loading.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/CreateModule.tsx b/src/app/course/[courseName]/modules/CreateModule.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/CreateModule.tsx rename to src/app/course/[courseName]/modules/CreateModule.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx b/src/app/course/[courseName]/modules/ExpandableModule.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx rename to src/app/course/[courseName]/modules/ExpandableModule.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/ModuleCanvasStatus.tsx b/src/app/course/[courseName]/modules/ModuleCanvasStatus.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/ModuleCanvasStatus.tsx rename to src/app/course/[courseName]/modules/ModuleCanvasStatus.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/ModuleList.tsx b/src/app/course/[courseName]/modules/ModuleList.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/ModuleList.tsx rename to src/app/course/[courseName]/modules/ModuleList.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx b/src/app/course/[courseName]/modules/NewItemForm.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx rename to src/app/course/[courseName]/modules/NewItemForm.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentButtons.tsx b/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentButtons.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentButtons.tsx rename to src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentButtons.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentPreview.tsx b/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentPreview.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentPreview.tsx rename to src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentPreview.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx b/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx rename to src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/layout.tsx b/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/layout.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/layout.tsx rename to src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/layout.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/loading.tsx b/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/loading.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/loading.tsx rename to src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/loading.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/page.tsx b/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/page.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/page.tsx rename to src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/page.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPage.tsx b/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPage.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPage.tsx rename to src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPage.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPageButtons.tsx b/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPageButtons.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPageButtons.tsx rename to src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPageButtons.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/PagePreview.tsx b/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/PagePreview.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/PagePreview.tsx rename to src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/PagePreview.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/layout.tsx b/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/layout.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/layout.tsx rename to src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/layout.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/loading.tsx b/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/loading.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/loading.tsx rename to src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/loading.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/page.tsx b/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/page.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/page.tsx rename to src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/page.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx b/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx rename to src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizButton.tsx b/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizButton.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizButton.tsx rename to src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizButton.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizPreview.tsx b/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizPreview.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizPreview.tsx rename to src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizPreview.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/layout.tsx b/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/layout.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/layout.tsx rename to src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/layout.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/loading.tsx b/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/loading.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/loading.tsx rename to src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/loading.tsx diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/page.tsx b/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/page.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/page.tsx rename to src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/page.tsx diff --git a/nextjs/src/app/course/[courseName]/page.tsx b/src/app/course/[courseName]/page.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/page.tsx rename to src/app/course/[courseName]/page.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/AllSettings.tsx b/src/app/course/[courseName]/settings/AllSettings.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/AllSettings.tsx rename to src/app/course/[courseName]/settings/AllSettings.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/AssignmentGroupManagement.tsx b/src/app/course/[courseName]/settings/AssignmentGroupManagement.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/AssignmentGroupManagement.tsx rename to src/app/course/[courseName]/settings/AssignmentGroupManagement.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/DaysOfWeekSettings.tsx b/src/app/course/[courseName]/settings/DaysOfWeekSettings.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/DaysOfWeekSettings.tsx rename to src/app/course/[courseName]/settings/DaysOfWeekSettings.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/DefaultDueTime.tsx b/src/app/course/[courseName]/settings/DefaultDueTime.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/DefaultDueTime.tsx rename to src/app/course/[courseName]/settings/DefaultDueTime.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/DefaultFileUploadTypes.tsx b/src/app/course/[courseName]/settings/DefaultFileUploadTypes.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/DefaultFileUploadTypes.tsx rename to src/app/course/[courseName]/settings/DefaultFileUploadTypes.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/DefaultLockOffset.tsx b/src/app/course/[courseName]/settings/DefaultLockOffset.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/DefaultLockOffset.tsx rename to src/app/course/[courseName]/settings/DefaultLockOffset.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/HolidayConfig.tsx b/src/app/course/[courseName]/settings/HolidayConfig.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/HolidayConfig.tsx rename to src/app/course/[courseName]/settings/HolidayConfig.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/SettingsHeader.tsx b/src/app/course/[courseName]/settings/SettingsHeader.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/SettingsHeader.tsx rename to src/app/course/[courseName]/settings/SettingsHeader.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/StartAndEndDate.tsx b/src/app/course/[courseName]/settings/StartAndEndDate.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/StartAndEndDate.tsx rename to src/app/course/[courseName]/settings/StartAndEndDate.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/SubmissionDefaults.tsx b/src/app/course/[courseName]/settings/SubmissionDefaults.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/SubmissionDefaults.tsx rename to src/app/course/[courseName]/settings/SubmissionDefaults.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/page.tsx b/src/app/course/[courseName]/settings/page.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/page.tsx rename to src/app/course/[courseName]/settings/page.tsx diff --git a/nextjs/src/app/course/[courseName]/settings/sharedSettings.ts b/src/app/course/[courseName]/settings/sharedSettings.ts similarity index 100% rename from nextjs/src/app/course/[courseName]/settings/sharedSettings.ts rename to src/app/course/[courseName]/settings/sharedSettings.ts diff --git a/nextjs/src/app/course/[courseName]/utils/useAuthoritativeUpdates.tsx b/src/app/course/[courseName]/utils/useAuthoritativeUpdates.tsx similarity index 100% rename from nextjs/src/app/course/[courseName]/utils/useAuthoritativeUpdates.tsx rename to src/app/course/[courseName]/utils/useAuthoritativeUpdates.tsx diff --git a/nextjs/src/app/globals.css b/src/app/globals.css similarity index 100% rename from nextjs/src/app/globals.css rename to src/app/globals.css diff --git a/nextjs/src/app/layout.tsx b/src/app/layout.tsx similarity index 100% rename from nextjs/src/app/layout.tsx rename to src/app/layout.tsx diff --git a/nextjs/src/app/loading.tsx b/src/app/loading.tsx similarity index 100% rename from nextjs/src/app/loading.tsx rename to src/app/loading.tsx diff --git a/nextjs/src/app/newCourse/AddNewCourse.tsx b/src/app/newCourse/AddNewCourse.tsx similarity index 100% rename from nextjs/src/app/newCourse/AddNewCourse.tsx rename to src/app/newCourse/AddNewCourse.tsx diff --git a/nextjs/src/app/newCourse/NewCourseForm.tsx b/src/app/newCourse/NewCourseForm.tsx similarity index 100% rename from nextjs/src/app/newCourse/NewCourseForm.tsx rename to src/app/newCourse/NewCourseForm.tsx diff --git a/nextjs/src/app/page.tsx b/src/app/page.tsx similarity index 100% rename from nextjs/src/app/page.tsx rename to src/app/page.tsx diff --git a/nextjs/src/app/providers.tsx b/src/app/providers.tsx similarity index 100% rename from nextjs/src/app/providers.tsx rename to src/app/providers.tsx diff --git a/nextjs/src/app/providersQueryClientUtils.ts b/src/app/providersQueryClientUtils.ts similarity index 100% rename from nextjs/src/app/providersQueryClientUtils.ts rename to src/app/providersQueryClientUtils.ts diff --git a/nextjs/src/app/todaysLectures/OneCourseLectures.tsx b/src/app/todaysLectures/OneCourseLectures.tsx similarity index 100% rename from nextjs/src/app/todaysLectures/OneCourseLectures.tsx rename to src/app/todaysLectures/OneCourseLectures.tsx diff --git a/nextjs/src/app/todaysLectures/TodaysLectures.tsx b/src/app/todaysLectures/TodaysLectures.tsx similarity index 100% rename from nextjs/src/app/todaysLectures/TodaysLectures.tsx rename to src/app/todaysLectures/TodaysLectures.tsx diff --git a/nextjs/src/components/ButtonSelect.tsx b/src/components/ButtonSelect.tsx similarity index 100% rename from nextjs/src/components/ButtonSelect.tsx rename to src/components/ButtonSelect.tsx diff --git a/nextjs/src/components/ClientOnly.tsx b/src/components/ClientOnly.tsx similarity index 100% rename from nextjs/src/components/ClientOnly.tsx rename to src/components/ClientOnly.tsx diff --git a/nextjs/src/components/Expandable.tsx b/src/components/Expandable.tsx similarity index 100% rename from nextjs/src/components/Expandable.tsx rename to src/components/Expandable.tsx diff --git a/nextjs/src/components/Modal.tsx b/src/components/Modal.tsx similarity index 100% rename from nextjs/src/components/Modal.tsx rename to src/components/Modal.tsx diff --git a/nextjs/src/components/Spinner.tsx b/src/components/Spinner.tsx similarity index 100% rename from nextjs/src/components/Spinner.tsx rename to src/components/Spinner.tsx diff --git a/nextjs/src/components/SuspenseAndErrorHandling.tsx b/src/components/SuspenseAndErrorHandling.tsx similarity index 100% rename from nextjs/src/components/SuspenseAndErrorHandling.tsx rename to src/components/SuspenseAndErrorHandling.tsx diff --git a/nextjs/src/components/TimePicker.tsx b/src/components/TimePicker.tsx similarity index 100% rename from nextjs/src/components/TimePicker.tsx rename to src/components/TimePicker.tsx diff --git a/nextjs/src/components/editor/InnerMonacoEditor.tsx b/src/components/editor/InnerMonacoEditor.tsx similarity index 100% rename from nextjs/src/components/editor/InnerMonacoEditor.tsx rename to src/components/editor/InnerMonacoEditor.tsx diff --git a/nextjs/src/components/editor/InnerMonacoEditorOther.tsx b/src/components/editor/InnerMonacoEditorOther.tsx similarity index 100% rename from nextjs/src/components/editor/InnerMonacoEditorOther.tsx rename to src/components/editor/InnerMonacoEditorOther.tsx diff --git a/nextjs/src/components/editor/MonacoEditor.css b/src/components/editor/MonacoEditor.css similarity index 100% rename from nextjs/src/components/editor/MonacoEditor.css rename to src/components/editor/MonacoEditor.css diff --git a/nextjs/src/components/editor/MonacoEditor.tsx b/src/components/editor/MonacoEditor.tsx similarity index 100% rename from nextjs/src/components/editor/MonacoEditor.tsx rename to src/components/editor/MonacoEditor.tsx diff --git a/nextjs/src/components/form/DayOfWeekInput.tsx b/src/components/form/DayOfWeekInput.tsx similarity index 100% rename from nextjs/src/components/form/DayOfWeekInput.tsx rename to src/components/form/DayOfWeekInput.tsx diff --git a/nextjs/src/components/form/SelectInput.tsx b/src/components/form/SelectInput.tsx similarity index 100% rename from nextjs/src/components/form/SelectInput.tsx rename to src/components/form/SelectInput.tsx diff --git a/nextjs/src/components/form/TextInput.tsx b/src/components/form/TextInput.tsx similarity index 100% rename from nextjs/src/components/form/TextInput.tsx rename to src/components/form/TextInput.tsx diff --git a/nextjs/src/components/icons/CheckIcon.tsx b/src/components/icons/CheckIcon.tsx similarity index 100% rename from nextjs/src/components/icons/CheckIcon.tsx rename to src/components/icons/CheckIcon.tsx diff --git a/nextjs/src/components/icons/ExpandIcon.tsx b/src/components/icons/ExpandIcon.tsx similarity index 100% rename from nextjs/src/components/icons/ExpandIcon.tsx rename to src/components/icons/ExpandIcon.tsx diff --git a/nextjs/src/components/realtime/ClientCacheInvalidation.tsx b/src/components/realtime/ClientCacheInvalidation.tsx similarity index 100% rename from nextjs/src/components/realtime/ClientCacheInvalidation.tsx rename to src/components/realtime/ClientCacheInvalidation.tsx diff --git a/nextjs/src/components/spinner.css b/src/components/spinner.css similarity index 100% rename from nextjs/src/components/spinner.css rename to src/components/spinner.css diff --git a/nextjs/src/hooks/canvas/canvasAssignmentHooks.ts b/src/hooks/canvas/canvasAssignmentHooks.ts similarity index 100% rename from nextjs/src/hooks/canvas/canvasAssignmentHooks.ts rename to src/hooks/canvas/canvasAssignmentHooks.ts diff --git a/nextjs/src/hooks/canvas/canvasCourseHooks.ts b/src/hooks/canvas/canvasCourseHooks.ts similarity index 100% rename from nextjs/src/hooks/canvas/canvasCourseHooks.ts rename to src/hooks/canvas/canvasCourseHooks.ts diff --git a/nextjs/src/hooks/canvas/canvasHooks.ts b/src/hooks/canvas/canvasHooks.ts similarity index 100% rename from nextjs/src/hooks/canvas/canvasHooks.ts rename to src/hooks/canvas/canvasHooks.ts diff --git a/nextjs/src/hooks/canvas/canvasModuleHooks.ts b/src/hooks/canvas/canvasModuleHooks.ts similarity index 100% rename from nextjs/src/hooks/canvas/canvasModuleHooks.ts rename to src/hooks/canvas/canvasModuleHooks.ts diff --git a/nextjs/src/hooks/canvas/canvasPageHooks.ts b/src/hooks/canvas/canvasPageHooks.ts similarity index 100% rename from nextjs/src/hooks/canvas/canvasPageHooks.ts rename to src/hooks/canvas/canvasPageHooks.ts diff --git a/nextjs/src/hooks/canvas/canvasQuizHooks.ts b/src/hooks/canvas/canvasQuizHooks.ts similarity index 100% rename from nextjs/src/hooks/canvas/canvasQuizHooks.ts rename to src/hooks/canvas/canvasQuizHooks.ts diff --git a/nextjs/src/hooks/localCourse/assignmentHooks.ts b/src/hooks/localCourse/assignmentHooks.ts similarity index 100% rename from nextjs/src/hooks/localCourse/assignmentHooks.ts rename to src/hooks/localCourse/assignmentHooks.ts diff --git a/nextjs/src/hooks/localCourse/lectureHooks.ts b/src/hooks/localCourse/lectureHooks.ts similarity index 100% rename from nextjs/src/hooks/localCourse/lectureHooks.ts rename to src/hooks/localCourse/lectureHooks.ts diff --git a/nextjs/src/hooks/localCourse/localCourseModuleHooks.ts b/src/hooks/localCourse/localCourseModuleHooks.ts similarity index 100% rename from nextjs/src/hooks/localCourse/localCourseModuleHooks.ts rename to src/hooks/localCourse/localCourseModuleHooks.ts diff --git a/nextjs/src/hooks/localCourse/localCoursesHooks.ts b/src/hooks/localCourse/localCoursesHooks.ts similarity index 100% rename from nextjs/src/hooks/localCourse/localCoursesHooks.ts rename to src/hooks/localCourse/localCoursesHooks.ts diff --git a/nextjs/src/hooks/localCourse/pageHooks.ts b/src/hooks/localCourse/pageHooks.ts similarity index 100% rename from nextjs/src/hooks/localCourse/pageHooks.ts rename to src/hooks/localCourse/pageHooks.ts diff --git a/nextjs/src/hooks/localCourse/quizHooks.ts b/src/hooks/localCourse/quizHooks.ts similarity index 100% rename from nextjs/src/hooks/localCourse/quizHooks.ts rename to src/hooks/localCourse/quizHooks.ts diff --git a/nextjs/src/hooks/localCourse/storageDirectoryHooks.ts b/src/hooks/localCourse/storageDirectoryHooks.ts similarity index 100% rename from nextjs/src/hooks/localCourse/storageDirectoryHooks.ts rename to src/hooks/localCourse/storageDirectoryHooks.ts diff --git a/nextjs/src/models/canvas/assignments/canvasAssignment.ts b/src/models/canvas/assignments/canvasAssignment.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasAssignment.ts rename to src/models/canvas/assignments/canvasAssignment.ts diff --git a/nextjs/src/models/canvas/assignments/canvasAssignmentDate.ts b/src/models/canvas/assignments/canvasAssignmentDate.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasAssignmentDate.ts rename to src/models/canvas/assignments/canvasAssignmentDate.ts diff --git a/nextjs/src/models/canvas/assignments/canvasAssignmentGroup.ts b/src/models/canvas/assignments/canvasAssignmentGroup.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasAssignmentGroup.ts rename to src/models/canvas/assignments/canvasAssignmentGroup.ts diff --git a/nextjs/src/models/canvas/assignments/canvasAssignmentOverride.ts b/src/models/canvas/assignments/canvasAssignmentOverride.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasAssignmentOverride.ts rename to src/models/canvas/assignments/canvasAssignmentOverride.ts diff --git a/nextjs/src/models/canvas/assignments/canvasExternalToolTagAttributes.ts b/src/models/canvas/assignments/canvasExternalToolTagAttributes.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasExternalToolTagAttributes.ts rename to src/models/canvas/assignments/canvasExternalToolTagAttributes.ts diff --git a/nextjs/src/models/canvas/assignments/canvasLockInfo.ts b/src/models/canvas/assignments/canvasLockInfo.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasLockInfo.ts rename to src/models/canvas/assignments/canvasLockInfo.ts diff --git a/nextjs/src/models/canvas/assignments/canvasRubric.ts b/src/models/canvas/assignments/canvasRubric.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasRubric.ts rename to src/models/canvas/assignments/canvasRubric.ts diff --git a/nextjs/src/models/canvas/assignments/canvasRubricAssociation.ts b/src/models/canvas/assignments/canvasRubricAssociation.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasRubricAssociation.ts rename to src/models/canvas/assignments/canvasRubricAssociation.ts diff --git a/nextjs/src/models/canvas/assignments/canvasRubricCreationResponse.ts b/src/models/canvas/assignments/canvasRubricCreationResponse.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasRubricCreationResponse.ts rename to src/models/canvas/assignments/canvasRubricCreationResponse.ts diff --git a/nextjs/src/models/canvas/assignments/canvasRubricCriteria.ts b/src/models/canvas/assignments/canvasRubricCriteria.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasRubricCriteria.ts rename to src/models/canvas/assignments/canvasRubricCriteria.ts diff --git a/nextjs/src/models/canvas/assignments/canvasTurnitinSettings.ts b/src/models/canvas/assignments/canvasTurnitinSettings.ts similarity index 100% rename from nextjs/src/models/canvas/assignments/canvasTurnitinSettings.ts rename to src/models/canvas/assignments/canvasTurnitinSettings.ts diff --git a/nextjs/src/models/canvas/courses/canvasCalendarLinkModel.ts b/src/models/canvas/courses/canvasCalendarLinkModel.ts similarity index 100% rename from nextjs/src/models/canvas/courses/canvasCalendarLinkModel.ts rename to src/models/canvas/courses/canvasCalendarLinkModel.ts diff --git a/nextjs/src/models/canvas/courses/canvasCourseModel.ts b/src/models/canvas/courses/canvasCourseModel.ts similarity index 100% rename from nextjs/src/models/canvas/courses/canvasCourseModel.ts rename to src/models/canvas/courses/canvasCourseModel.ts diff --git a/nextjs/src/models/canvas/courses/canvasCourseProgressModel.ts b/src/models/canvas/courses/canvasCourseProgressModel.ts similarity index 100% rename from nextjs/src/models/canvas/courses/canvasCourseProgressModel.ts rename to src/models/canvas/courses/canvasCourseProgressModel.ts diff --git a/nextjs/src/models/canvas/courses/canvasCourseSettingsModel.ts b/src/models/canvas/courses/canvasCourseSettingsModel.ts similarity index 100% rename from nextjs/src/models/canvas/courses/canvasCourseSettingsModel.ts rename to src/models/canvas/courses/canvasCourseSettingsModel.ts diff --git a/nextjs/src/models/canvas/courses/canvasTermModel.ts b/src/models/canvas/courses/canvasTermModel.ts similarity index 100% rename from nextjs/src/models/canvas/courses/canvasTermModel.ts rename to src/models/canvas/courses/canvasTermModel.ts diff --git a/nextjs/src/models/canvas/discussions/canvasDiscussionModelTopic.ts b/src/models/canvas/discussions/canvasDiscussionModelTopic.ts similarity index 100% rename from nextjs/src/models/canvas/discussions/canvasDiscussionModelTopic.ts rename to src/models/canvas/discussions/canvasDiscussionModelTopic.ts diff --git a/nextjs/src/models/canvas/discussions/canvasFileAttachmentModel.ts b/src/models/canvas/discussions/canvasFileAttachmentModel.ts similarity index 100% rename from nextjs/src/models/canvas/discussions/canvasFileAttachmentModel.ts rename to src/models/canvas/discussions/canvasFileAttachmentModel.ts diff --git a/nextjs/src/models/canvas/enrollmentTerms/canvasEnrollmentTermModel.ts b/src/models/canvas/enrollmentTerms/canvasEnrollmentTermModel.ts similarity index 100% rename from nextjs/src/models/canvas/enrollmentTerms/canvasEnrollmentTermModel.ts rename to src/models/canvas/enrollmentTerms/canvasEnrollmentTermModel.ts diff --git a/nextjs/src/models/canvas/enrollments/canvasEnrollmentModel.ts b/src/models/canvas/enrollments/canvasEnrollmentModel.ts similarity index 100% rename from nextjs/src/models/canvas/enrollments/canvasEnrollmentModel.ts rename to src/models/canvas/enrollments/canvasEnrollmentModel.ts diff --git a/nextjs/src/models/canvas/enrollments/canvasGradeModel.ts b/src/models/canvas/enrollments/canvasGradeModel.ts similarity index 100% rename from nextjs/src/models/canvas/enrollments/canvasGradeModel.ts rename to src/models/canvas/enrollments/canvasGradeModel.ts diff --git a/nextjs/src/models/canvas/modules/canvasModule.ts b/src/models/canvas/modules/canvasModule.ts similarity index 100% rename from nextjs/src/models/canvas/modules/canvasModule.ts rename to src/models/canvas/modules/canvasModule.ts diff --git a/nextjs/src/models/canvas/modules/canvasModuleItems.ts b/src/models/canvas/modules/canvasModuleItems.ts similarity index 100% rename from nextjs/src/models/canvas/modules/canvasModuleItems.ts rename to src/models/canvas/modules/canvasModuleItems.ts diff --git a/nextjs/src/models/canvas/pages/canvasPageModel.ts b/src/models/canvas/pages/canvasPageModel.ts similarity index 100% rename from nextjs/src/models/canvas/pages/canvasPageModel.ts rename to src/models/canvas/pages/canvasPageModel.ts diff --git a/nextjs/src/models/canvas/quizzes/canvasQuizAnswerModel.ts b/src/models/canvas/quizzes/canvasQuizAnswerModel.ts similarity index 100% rename from nextjs/src/models/canvas/quizzes/canvasQuizAnswerModel.ts rename to src/models/canvas/quizzes/canvasQuizAnswerModel.ts diff --git a/nextjs/src/models/canvas/quizzes/canvasQuizModel.ts b/src/models/canvas/quizzes/canvasQuizModel.ts similarity index 100% rename from nextjs/src/models/canvas/quizzes/canvasQuizModel.ts rename to src/models/canvas/quizzes/canvasQuizModel.ts diff --git a/nextjs/src/models/canvas/quizzes/canvasQuizPermission.ts b/src/models/canvas/quizzes/canvasQuizPermission.ts similarity index 100% rename from nextjs/src/models/canvas/quizzes/canvasQuizPermission.ts rename to src/models/canvas/quizzes/canvasQuizPermission.ts diff --git a/nextjs/src/models/canvas/quizzes/canvasQuizQuestionModel.ts b/src/models/canvas/quizzes/canvasQuizQuestionModel.ts similarity index 100% rename from nextjs/src/models/canvas/quizzes/canvasQuizQuestionModel.ts rename to src/models/canvas/quizzes/canvasQuizQuestionModel.ts diff --git a/nextjs/src/models/canvas/submissions/canvasSubmissionModel.ts b/src/models/canvas/submissions/canvasSubmissionModel.ts similarity index 100% rename from nextjs/src/models/canvas/submissions/canvasSubmissionModel.ts rename to src/models/canvas/submissions/canvasSubmissionModel.ts diff --git a/nextjs/src/models/canvas/users/canvasUserModel.ts b/src/models/canvas/users/canvasUserModel.ts similarity index 100% rename from nextjs/src/models/canvas/users/canvasUserModel.ts rename to src/models/canvas/users/canvasUserModel.ts diff --git a/nextjs/src/models/canvas/users/userDisplayModel.ts b/src/models/canvas/users/userDisplayModel.ts similarity index 100% rename from nextjs/src/models/canvas/users/userDisplayModel.ts rename to src/models/canvas/users/userDisplayModel.ts diff --git a/nextjs/src/models/local/IModuleItem.ts b/src/models/local/IModuleItem.ts similarity index 100% rename from nextjs/src/models/local/IModuleItem.ts rename to src/models/local/IModuleItem.ts diff --git a/nextjs/src/models/local/assignment/assignmentSubmissionType.ts b/src/models/local/assignment/assignmentSubmissionType.ts similarity index 100% rename from nextjs/src/models/local/assignment/assignmentSubmissionType.ts rename to src/models/local/assignment/assignmentSubmissionType.ts diff --git a/nextjs/src/models/local/assignment/localAssignment.ts b/src/models/local/assignment/localAssignment.ts similarity index 100% rename from nextjs/src/models/local/assignment/localAssignment.ts rename to src/models/local/assignment/localAssignment.ts diff --git a/nextjs/src/models/local/assignment/localAssignmentGroup.ts b/src/models/local/assignment/localAssignmentGroup.ts similarity index 100% rename from nextjs/src/models/local/assignment/localAssignmentGroup.ts rename to src/models/local/assignment/localAssignmentGroup.ts diff --git a/nextjs/src/models/local/assignment/rubricItem.ts b/src/models/local/assignment/rubricItem.ts similarity index 100% rename from nextjs/src/models/local/assignment/rubricItem.ts rename to src/models/local/assignment/rubricItem.ts diff --git a/nextjs/src/models/local/assignment/utils/assignmentMarkdownParser.ts b/src/models/local/assignment/utils/assignmentMarkdownParser.ts similarity index 100% rename from nextjs/src/models/local/assignment/utils/assignmentMarkdownParser.ts rename to src/models/local/assignment/utils/assignmentMarkdownParser.ts diff --git a/nextjs/src/models/local/assignment/utils/assignmentMarkdownSerializer.ts b/src/models/local/assignment/utils/assignmentMarkdownSerializer.ts similarity index 100% rename from nextjs/src/models/local/assignment/utils/assignmentMarkdownSerializer.ts rename to src/models/local/assignment/utils/assignmentMarkdownSerializer.ts diff --git a/nextjs/src/models/local/assignment/utils/assignmentPointsUtils.ts b/src/models/local/assignment/utils/assignmentPointsUtils.ts similarity index 100% rename from nextjs/src/models/local/assignment/utils/assignmentPointsUtils.ts rename to src/models/local/assignment/utils/assignmentPointsUtils.ts diff --git a/nextjs/src/models/local/assignment/utils/markdownUtils.ts b/src/models/local/assignment/utils/markdownUtils.ts similarity index 100% rename from nextjs/src/models/local/assignment/utils/markdownUtils.ts rename to src/models/local/assignment/utils/markdownUtils.ts diff --git a/nextjs/src/models/local/courseItemTypes.ts b/src/models/local/courseItemTypes.ts similarity index 100% rename from nextjs/src/models/local/courseItemTypes.ts rename to src/models/local/courseItemTypes.ts diff --git a/nextjs/src/models/local/lecture.ts b/src/models/local/lecture.ts similarity index 100% rename from nextjs/src/models/local/lecture.ts rename to src/models/local/lecture.ts diff --git a/nextjs/src/models/local/localCourseSettings.ts b/src/models/local/localCourseSettings.ts similarity index 100% rename from nextjs/src/models/local/localCourseSettings.ts rename to src/models/local/localCourseSettings.ts diff --git a/nextjs/src/models/local/localModules.ts b/src/models/local/localModules.ts similarity index 100% rename from nextjs/src/models/local/localModules.ts rename to src/models/local/localModules.ts diff --git a/nextjs/src/models/local/page/localCoursePage.ts b/src/models/local/page/localCoursePage.ts similarity index 100% rename from nextjs/src/models/local/page/localCoursePage.ts rename to src/models/local/page/localCoursePage.ts diff --git a/nextjs/src/models/local/quiz/localQuiz.ts b/src/models/local/quiz/localQuiz.ts similarity index 100% rename from nextjs/src/models/local/quiz/localQuiz.ts rename to src/models/local/quiz/localQuiz.ts diff --git a/nextjs/src/models/local/quiz/localQuizQuestion.ts b/src/models/local/quiz/localQuizQuestion.ts similarity index 100% rename from nextjs/src/models/local/quiz/localQuizQuestion.ts rename to src/models/local/quiz/localQuizQuestion.ts diff --git a/nextjs/src/models/local/quiz/localQuizQuestionAnswer.ts b/src/models/local/quiz/localQuizQuestionAnswer.ts similarity index 100% rename from nextjs/src/models/local/quiz/localQuizQuestionAnswer.ts rename to src/models/local/quiz/localQuizQuestionAnswer.ts diff --git a/nextjs/src/models/local/quiz/utils/quizMarkdownUtils.ts b/src/models/local/quiz/utils/quizMarkdownUtils.ts similarity index 100% rename from nextjs/src/models/local/quiz/utils/quizMarkdownUtils.ts rename to src/models/local/quiz/utils/quizMarkdownUtils.ts diff --git a/nextjs/src/models/local/quiz/utils/quizQuestionAnswerMarkdownUtils.ts b/src/models/local/quiz/utils/quizQuestionAnswerMarkdownUtils.ts similarity index 100% rename from nextjs/src/models/local/quiz/utils/quizQuestionAnswerMarkdownUtils.ts rename to src/models/local/quiz/utils/quizQuestionAnswerMarkdownUtils.ts diff --git a/nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts b/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts similarity index 100% rename from nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts rename to src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts diff --git a/nextjs/src/models/local/tests/assignmentMarkdown.test.ts b/src/models/local/tests/assignmentMarkdown.test.ts similarity index 100% rename from nextjs/src/models/local/tests/assignmentMarkdown.test.ts rename to src/models/local/tests/assignmentMarkdown.test.ts diff --git a/nextjs/src/models/local/tests/pageMarkdown.test.ts b/src/models/local/tests/pageMarkdown.test.ts similarity index 100% rename from nextjs/src/models/local/tests/pageMarkdown.test.ts rename to src/models/local/tests/pageMarkdown.test.ts diff --git a/nextjs/src/models/local/tests/quizMarkdown/matchingAnswerErrors.test.ts b/src/models/local/tests/quizMarkdown/matchingAnswerErrors.test.ts similarity index 100% rename from nextjs/src/models/local/tests/quizMarkdown/matchingAnswerErrors.test.ts rename to src/models/local/tests/quizMarkdown/matchingAnswerErrors.test.ts diff --git a/nextjs/src/models/local/tests/quizMarkdown/matchingAnswers.test.ts b/src/models/local/tests/quizMarkdown/matchingAnswers.test.ts similarity index 100% rename from nextjs/src/models/local/tests/quizMarkdown/matchingAnswers.test.ts rename to src/models/local/tests/quizMarkdown/matchingAnswers.test.ts diff --git a/nextjs/src/models/local/tests/quizMarkdown/multipleAnswers.test.ts b/src/models/local/tests/quizMarkdown/multipleAnswers.test.ts similarity index 100% rename from nextjs/src/models/local/tests/quizMarkdown/multipleAnswers.test.ts rename to src/models/local/tests/quizMarkdown/multipleAnswers.test.ts diff --git a/nextjs/src/models/local/tests/quizMarkdown/multipleChoice.test.ts b/src/models/local/tests/quizMarkdown/multipleChoice.test.ts similarity index 100% rename from nextjs/src/models/local/tests/quizMarkdown/multipleChoice.test.ts rename to src/models/local/tests/quizMarkdown/multipleChoice.test.ts diff --git a/nextjs/src/models/local/tests/quizMarkdown/quizDeterministicChecks.test.ts b/src/models/local/tests/quizMarkdown/quizDeterministicChecks.test.ts similarity index 100% rename from nextjs/src/models/local/tests/quizMarkdown/quizDeterministicChecks.test.ts rename to src/models/local/tests/quizMarkdown/quizDeterministicChecks.test.ts diff --git a/nextjs/src/models/local/tests/quizMarkdown/quizMarkdown.test.ts b/src/models/local/tests/quizMarkdown/quizMarkdown.test.ts similarity index 100% rename from nextjs/src/models/local/tests/quizMarkdown/quizMarkdown.test.ts rename to src/models/local/tests/quizMarkdown/quizMarkdown.test.ts diff --git a/nextjs/src/models/local/tests/quizMarkdown/testAnswer.test.ts b/src/models/local/tests/quizMarkdown/testAnswer.test.ts similarity index 100% rename from nextjs/src/models/local/tests/quizMarkdown/testAnswer.test.ts rename to src/models/local/tests/quizMarkdown/testAnswer.test.ts diff --git a/nextjs/src/models/local/tests/rubricMarkdown.test.ts b/src/models/local/tests/rubricMarkdown.test.ts similarity index 100% rename from nextjs/src/models/local/tests/rubricMarkdown.test.ts rename to src/models/local/tests/rubricMarkdown.test.ts diff --git a/nextjs/src/models/local/tests/testHolidayParsing.test.ts b/src/models/local/tests/testHolidayParsing.test.ts similarity index 100% rename from nextjs/src/models/local/tests/testHolidayParsing.test.ts rename to src/models/local/tests/testHolidayParsing.test.ts diff --git a/nextjs/src/models/local/tests/testSemesterImport.test.ts b/src/models/local/tests/testSemesterImport.test.ts similarity index 100% rename from nextjs/src/models/local/tests/testSemesterImport.test.ts rename to src/models/local/tests/testSemesterImport.test.ts diff --git a/nextjs/src/models/local/tests/timeUtils.test.ts b/src/models/local/tests/timeUtils.test.ts similarity index 100% rename from nextjs/src/models/local/tests/timeUtils.test.ts rename to src/models/local/tests/timeUtils.test.ts diff --git a/nextjs/src/models/local/utils/lectureUtils.ts b/src/models/local/utils/lectureUtils.ts similarity index 100% rename from nextjs/src/models/local/utils/lectureUtils.ts rename to src/models/local/utils/lectureUtils.ts diff --git a/nextjs/src/models/local/utils/semesterTransferUtils.ts b/src/models/local/utils/semesterTransferUtils.ts similarity index 100% rename from nextjs/src/models/local/utils/semesterTransferUtils.ts rename to src/models/local/utils/semesterTransferUtils.ts diff --git a/nextjs/src/models/local/utils/settingsUtils.tsx b/src/models/local/utils/settingsUtils.tsx similarity index 100% rename from nextjs/src/models/local/utils/settingsUtils.tsx rename to src/models/local/utils/settingsUtils.tsx diff --git a/nextjs/src/models/local/utils/timeUtils.ts b/src/models/local/utils/timeUtils.ts similarity index 100% rename from nextjs/src/models/local/utils/timeUtils.ts rename to src/models/local/utils/timeUtils.ts diff --git a/nextjs/src/services/axiosUtils.ts b/src/services/axiosUtils.ts similarity index 100% rename from nextjs/src/services/axiosUtils.ts rename to src/services/axiosUtils.ts diff --git a/nextjs/src/services/canvas/canvasAssignmentGroupService.ts b/src/services/canvas/canvasAssignmentGroupService.ts similarity index 100% rename from nextjs/src/services/canvas/canvasAssignmentGroupService.ts rename to src/services/canvas/canvasAssignmentGroupService.ts diff --git a/nextjs/src/services/canvas/canvasAssignmentService.ts b/src/services/canvas/canvasAssignmentService.ts similarity index 100% rename from nextjs/src/services/canvas/canvasAssignmentService.ts rename to src/services/canvas/canvasAssignmentService.ts diff --git a/nextjs/src/services/canvas/canvasModuleService.ts b/src/services/canvas/canvasModuleService.ts similarity index 100% rename from nextjs/src/services/canvas/canvasModuleService.ts rename to src/services/canvas/canvasModuleService.ts diff --git a/nextjs/src/services/canvas/canvasPageService.ts b/src/services/canvas/canvasPageService.ts similarity index 100% rename from nextjs/src/services/canvas/canvasPageService.ts rename to src/services/canvas/canvasPageService.ts diff --git a/nextjs/src/services/canvas/canvasQuizService.ts b/src/services/canvas/canvasQuizService.ts similarity index 100% rename from nextjs/src/services/canvas/canvasQuizService.ts rename to src/services/canvas/canvasQuizService.ts diff --git a/nextjs/src/services/canvas/canvasRubricUtils.ts b/src/services/canvas/canvasRubricUtils.ts similarity index 100% rename from nextjs/src/services/canvas/canvasRubricUtils.ts rename to src/services/canvas/canvasRubricUtils.ts diff --git a/nextjs/src/services/canvas/canvasService.ts b/src/services/canvas/canvasService.ts similarity index 100% rename from nextjs/src/services/canvas/canvasService.ts rename to src/services/canvas/canvasService.ts diff --git a/nextjs/src/services/canvas/canvasServiceUtils.ts b/src/services/canvas/canvasServiceUtils.ts similarity index 100% rename from nextjs/src/services/canvas/canvasServiceUtils.ts rename to src/services/canvas/canvasServiceUtils.ts diff --git a/nextjs/src/services/canvas/canvasWebRequestor.ts b/src/services/canvas/canvasWebRequestor.ts similarity index 100% rename from nextjs/src/services/canvas/canvasWebRequestor.ts rename to src/services/canvas/canvasWebRequestor.ts diff --git a/nextjs/src/services/canvas/files/canvasFileService.ts b/src/services/canvas/files/canvasFileService.ts similarity index 100% rename from nextjs/src/services/canvas/files/canvasFileService.ts rename to src/services/canvas/files/canvasFileService.ts diff --git a/nextjs/src/services/canvas/rubric.test.ts b/src/services/canvas/rubric.test.ts similarity index 100% rename from nextjs/src/services/canvas/rubric.test.ts rename to src/services/canvas/rubric.test.ts diff --git a/nextjs/src/services/fileStorage/assignmentsFileStorageService.ts b/src/services/fileStorage/assignmentsFileStorageService.ts similarity index 100% rename from nextjs/src/services/fileStorage/assignmentsFileStorageService.ts rename to src/services/fileStorage/assignmentsFileStorageService.ts diff --git a/nextjs/src/services/fileStorage/courseItemFileStorageService.ts b/src/services/fileStorage/courseItemFileStorageService.ts similarity index 100% rename from nextjs/src/services/fileStorage/courseItemFileStorageService.ts rename to src/services/fileStorage/courseItemFileStorageService.ts diff --git a/nextjs/src/services/fileStorage/fileStorageService.ts b/src/services/fileStorage/fileStorageService.ts similarity index 100% rename from nextjs/src/services/fileStorage/fileStorageService.ts rename to src/services/fileStorage/fileStorageService.ts diff --git a/nextjs/src/services/fileStorage/lectureFileStorageService.ts b/src/services/fileStorage/lectureFileStorageService.ts similarity index 100% rename from nextjs/src/services/fileStorage/lectureFileStorageService.ts rename to src/services/fileStorage/lectureFileStorageService.ts diff --git a/nextjs/src/services/fileStorage/moduleFileStorageService.ts b/src/services/fileStorage/moduleFileStorageService.ts similarity index 100% rename from nextjs/src/services/fileStorage/moduleFileStorageService.ts rename to src/services/fileStorage/moduleFileStorageService.ts diff --git a/nextjs/src/services/fileStorage/pageFileStorageService.ts b/src/services/fileStorage/pageFileStorageService.ts similarity index 100% rename from nextjs/src/services/fileStorage/pageFileStorageService.ts rename to src/services/fileStorage/pageFileStorageService.ts diff --git a/nextjs/src/services/fileStorage/quizFileStorageService.ts b/src/services/fileStorage/quizFileStorageService.ts similarity index 100% rename from nextjs/src/services/fileStorage/quizFileStorageService.ts rename to src/services/fileStorage/quizFileStorageService.ts diff --git a/nextjs/src/services/fileStorage/settingsFileStorageService.ts b/src/services/fileStorage/settingsFileStorageService.ts similarity index 100% rename from nextjs/src/services/fileStorage/settingsFileStorageService.ts rename to src/services/fileStorage/settingsFileStorageService.ts diff --git a/nextjs/src/services/fileStorage/utils/fileSystemUtils.ts b/src/services/fileStorage/utils/fileSystemUtils.ts similarity index 100% rename from nextjs/src/services/fileStorage/utils/fileSystemUtils.ts rename to src/services/fileStorage/utils/fileSystemUtils.ts diff --git a/nextjs/src/services/fileStorage/utils/lectureUtils.ts b/src/services/fileStorage/utils/lectureUtils.ts similarity index 100% rename from nextjs/src/services/fileStorage/utils/lectureUtils.ts rename to src/services/fileStorage/utils/lectureUtils.ts diff --git a/nextjs/src/services/htmlMarkdownUtils.ts b/src/services/htmlMarkdownUtils.ts similarity index 100% rename from nextjs/src/services/htmlMarkdownUtils.ts rename to src/services/htmlMarkdownUtils.ts diff --git a/nextjs/src/services/serverFunctions/TrpcProvider.tsx b/src/services/serverFunctions/TrpcProvider.tsx similarity index 100% rename from nextjs/src/services/serverFunctions/TrpcProvider.tsx rename to src/services/serverFunctions/TrpcProvider.tsx diff --git a/nextjs/src/services/serverFunctions/context.ts b/src/services/serverFunctions/context.ts similarity index 100% rename from nextjs/src/services/serverFunctions/context.ts rename to src/services/serverFunctions/context.ts diff --git a/nextjs/src/services/serverFunctions/procedures/public.ts b/src/services/serverFunctions/procedures/public.ts similarity index 100% rename from nextjs/src/services/serverFunctions/procedures/public.ts rename to src/services/serverFunctions/procedures/public.ts diff --git a/nextjs/src/services/serverFunctions/router/app.ts b/src/services/serverFunctions/router/app.ts similarity index 100% rename from nextjs/src/services/serverFunctions/router/app.ts rename to src/services/serverFunctions/router/app.ts diff --git a/nextjs/src/services/serverFunctions/router/assignmentRouter.ts b/src/services/serverFunctions/router/assignmentRouter.ts similarity index 100% rename from nextjs/src/services/serverFunctions/router/assignmentRouter.ts rename to src/services/serverFunctions/router/assignmentRouter.ts diff --git a/nextjs/src/services/serverFunctions/router/canvasFileRouter.ts b/src/services/serverFunctions/router/canvasFileRouter.ts similarity index 100% rename from nextjs/src/services/serverFunctions/router/canvasFileRouter.ts rename to src/services/serverFunctions/router/canvasFileRouter.ts diff --git a/nextjs/src/services/serverFunctions/router/directoriesRouter.ts b/src/services/serverFunctions/router/directoriesRouter.ts similarity index 100% rename from nextjs/src/services/serverFunctions/router/directoriesRouter.ts rename to src/services/serverFunctions/router/directoriesRouter.ts diff --git a/nextjs/src/services/serverFunctions/router/lectureRouter.ts b/src/services/serverFunctions/router/lectureRouter.ts similarity index 100% rename from nextjs/src/services/serverFunctions/router/lectureRouter.ts rename to src/services/serverFunctions/router/lectureRouter.ts diff --git a/nextjs/src/services/serverFunctions/router/moduleRouter.ts b/src/services/serverFunctions/router/moduleRouter.ts similarity index 100% rename from nextjs/src/services/serverFunctions/router/moduleRouter.ts rename to src/services/serverFunctions/router/moduleRouter.ts diff --git a/nextjs/src/services/serverFunctions/router/pageRouter.ts b/src/services/serverFunctions/router/pageRouter.ts similarity index 100% rename from nextjs/src/services/serverFunctions/router/pageRouter.ts rename to src/services/serverFunctions/router/pageRouter.ts diff --git a/nextjs/src/services/serverFunctions/router/quizRouter.ts b/src/services/serverFunctions/router/quizRouter.ts similarity index 100% rename from nextjs/src/services/serverFunctions/router/quizRouter.ts rename to src/services/serverFunctions/router/quizRouter.ts diff --git a/nextjs/src/services/serverFunctions/router/settingsRouter.ts b/src/services/serverFunctions/router/settingsRouter.ts similarity index 100% rename from nextjs/src/services/serverFunctions/router/settingsRouter.ts rename to src/services/serverFunctions/router/settingsRouter.ts diff --git a/nextjs/src/services/serverFunctions/trpcClient.ts b/src/services/serverFunctions/trpcClient.ts similarity index 100% rename from nextjs/src/services/serverFunctions/trpcClient.ts rename to src/services/serverFunctions/trpcClient.ts diff --git a/nextjs/src/services/serverFunctions/trpcSetup.ts b/src/services/serverFunctions/trpcSetup.ts similarity index 100% rename from nextjs/src/services/serverFunctions/trpcSetup.ts rename to src/services/serverFunctions/trpcSetup.ts diff --git a/nextjs/src/services/tests/fileStorage.test.ts b/src/services/tests/fileStorage.test.ts similarity index 100% rename from nextjs/src/services/tests/fileStorage.test.ts rename to src/services/tests/fileStorage.test.ts diff --git a/nextjs/src/services/tests/fileStorageParsingErrors.test.ts b/src/services/tests/fileStorageParsingErrors.test.ts similarity index 100% rename from nextjs/src/services/tests/fileStorageParsingErrors.test.ts rename to src/services/tests/fileStorageParsingErrors.test.ts diff --git a/nextjs/src/services/tests/lectureStorage.test.ts b/src/services/tests/lectureStorage.test.ts similarity index 100% rename from nextjs/src/services/tests/lectureStorage.test.ts rename to src/services/tests/lectureStorage.test.ts diff --git a/nextjs/src/services/urlUtils.ts b/src/services/urlUtils.ts similarity index 100% rename from nextjs/src/services/urlUtils.ts rename to src/services/urlUtils.ts diff --git a/nextjs/src/services/utils/htmlIsCloseEnough.test.ts b/src/services/utils/htmlIsCloseEnough.test.ts similarity index 100% rename from nextjs/src/services/utils/htmlIsCloseEnough.test.ts rename to src/services/utils/htmlIsCloseEnough.test.ts diff --git a/nextjs/src/services/utils/htmlIsCloseEnough.ts b/src/services/utils/htmlIsCloseEnough.ts similarity index 100% rename from nextjs/src/services/utils/htmlIsCloseEnough.ts rename to src/services/utils/htmlIsCloseEnough.ts diff --git a/nextjs/src/services/utils/queryClient.tsx b/src/services/utils/queryClient.tsx similarity index 100% rename from nextjs/src/services/utils/queryClient.tsx rename to src/services/utils/queryClient.tsx diff --git a/nextjs/src/services/withErrorHandling.ts b/src/services/withErrorHandling.ts similarity index 100% rename from nextjs/src/services/withErrorHandling.ts rename to src/services/withErrorHandling.ts diff --git a/nextjs/src/websocket.js b/src/websocket.js similarity index 100% rename from nextjs/src/websocket.js rename to src/websocket.js diff --git a/nextjs/tailwind.config.ts b/tailwind.config.ts similarity index 100% rename from nextjs/tailwind.config.ts rename to tailwind.config.ts diff --git a/tmptxt.txt b/tmptxt.txt deleted file mode 100644 index 4673940..0000000 --- a/tmptxt.txt +++ /dev/null @@ -1,44 +0,0 @@ - Expected markdown "Name: Test Quiz -Id: string -CanvasId: 8324723 -LockAtDueDate: true -LockAt: 12/31/9999 11:59:59 PM -DueAt: 12/31/9999 11:59:59 PM -ShuffleAnswers: true -OneQuestionAtATime: false -LocalAssignmentGroupId: someId -AllowedAttempts: -1 -Description: desc ---- -Points: 2 -`some type` of question - -with many - -``` -lines -``` - - -- *true -- false - -endline ---- - -" to contain " -Points: 2 -`some type` of question - -with many - -``` -lines -``` - -- *true -- false - -endline ---- -". \ No newline at end of file diff --git a/nextjs/tsconfig.json b/tsconfig.json similarity index 100% rename from nextjs/tsconfig.json rename to tsconfig.json diff --git a/nextjs/vitest.config.ts b/vitest.config.ts similarity index 100% rename from nextjs/vitest.config.ts rename to vitest.config.ts