mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
Compare commits
118 Commits
v1
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a350f5629 | ||
|
|
c29c7c0853 | ||
| 33120c40a5 | |||
| 2ec3d9349e | |||
| 5e088fb4eb | |||
|
|
aae9e7bba4 | ||
| 58175c1426 | |||
| 03529f875a | |||
|
|
95c9d07592 | ||
|
|
9918b63a1e | ||
|
|
f808a517d3 | ||
|
|
b47fa4cff5 | ||
|
|
efe2060fcd | ||
|
|
c60ba92f28 | ||
|
|
b65cfa73d7 | ||
|
|
dbc7887d82 | ||
| ecb5f6d70f | |||
| 523a05d45e | |||
| 5f408749e4 | |||
| 994d6e9a03 | |||
| d1a768393c | |||
| 224cc9cd2a | |||
| e07a12f622 | |||
| 54e4d7b4a1 | |||
| e8de00a2b1 | |||
| 762a51d6da | |||
| 5715b081a9 | |||
| c5759c0bec | |||
| f7357e4c08 | |||
| 60b2ad7959 | |||
| a94087dd98 | |||
| 99f491f16e | |||
| 815f929c2d | |||
| c37ad0708e | |||
| aa15b2b335 | |||
| 1885431574 | |||
| 3e371247d6 | |||
| d5a40e52d9 | |||
| c95c40f9e7 | |||
| 46e0c36916 | |||
| 704a5ae404 | |||
| 67b67100c1 | |||
| 01d137efcf | |||
| cea6aef453 | |||
| 746253b6c2 | |||
| 5ab371334e | |||
| 42ce579eee | |||
| 9aec082467 | |||
| d200c114d3 | |||
| 0efecad60e | |||
| 5f4417083a | |||
| 31ab49ed16 | |||
| bc2008f455 | |||
| 2432e0408f | |||
| c93c0b0e22 | |||
| 2b11106f02 | |||
| 43ed57e558 | |||
| 57b7d8ac1e | |||
| c33b40b55e | |||
| c39d7ca4d7 | |||
| a128107094 | |||
| abf6d5a9a2 | |||
| bc8b9ca0c4 | |||
| 00cafeec0a | |||
| 5a56d26b4d | |||
| d8f17faaae | |||
| 82ec0c0b28 | |||
| 05f354ac9e | |||
| a9bc8ef390 | |||
| 0bd55d3f67 | |||
| b35ba0f939 | |||
| 0fef2a6b87 | |||
| cda4be67fa | |||
| 32e77d5f4f | |||
| 408246be7f | |||
| 54e071b053 | |||
| 777d1e4659 | |||
| 719106b6bb | |||
| 3ce0eff2f8 | |||
| f0c147cd6a | |||
| a60008c6d7 | |||
| b2514bb356 | |||
| 4ed40bd24b | |||
| 3340dcd264 | |||
| f96dcb070f | |||
| da116abfae | |||
| 4005c85d60 | |||
| d581569c7a | |||
|
|
90fcca7bbe | ||
|
|
5f11fe76f1 | ||
|
|
ade3f4dca4 | ||
| ada36c143c | |||
| 6774624739 | |||
| cc2001565e | |||
| a722e7291b | |||
| a494e315d2 | |||
| ad4b059a17 | |||
| f142b85424 | |||
| b22d09da1a | |||
| 8e825960f6 | |||
| 41c9cc7556 | |||
| f8ca0bca3c | |||
| 563fe01383 | |||
| 78c1e80380 | |||
| 30a8581587 | |||
| c08b6857ed | |||
| 8547b99092 | |||
| df57e93cf6 | |||
| 0f1d999e16 | |||
| b020673282 | |||
| 7b1201c2ba | |||
| 9a8c5bff91 | |||
| c557bbcc28 | |||
| 068c2b6983 | |||
| 7993342ee7 | |||
| 5b4f5d3677 | |||
| 2460936470 | |||
| 576ee02afb |
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2
|
||||
}
|
||||
@@ -6,5 +6,9 @@ temp/
|
||||
build.sh
|
||||
run.sh
|
||||
README.md
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
.next/
|
||||
.pnpm-store/
|
||||
|
||||
|
||||
348
.editorconfig
348
.editorconfig
@@ -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
|
||||
|
||||
31
.github/workflows/docker-deploy.yml
vendored
Normal file
31
.github/workflows/docker-deploy.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Deploy to Docker Hub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, development, staging ]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push Docker image
|
||||
run: |
|
||||
chmod +x ./build.sh
|
||||
./build.sh -t -p -b "$BRANCH_NAME"
|
||||
52
.gitignore
vendored
52
.gitignore
vendored
@@ -1,8 +1,46 @@
|
||||
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/
|
||||
|
||||
**/*.env
|
||||
.env
|
||||
|
||||
# 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/
|
||||
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
]
|
||||
}
|
||||
35
.vscode/launch.json
vendored
35
.vscode/launch.json
vendored
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"dotnet.defaultSolution": "canvasManagement.sln"
|
||||
}
|
||||
41
.vscode/tasks.json
vendored
41
.vscode/tasks.json
vendored
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,6 +11,7 @@ COPY . .
|
||||
|
||||
RUN mkdir -p storage
|
||||
RUN rm -rf /app/storage/*
|
||||
ENV NEXT_PUBLIC_ENABLE_FILE_SYNC=true
|
||||
RUN pnpm run build
|
||||
|
||||
FROM node:22-alpine AS production
|
||||
@@ -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<RedundantEnrollmentTermsResponse>(canvasContentResponse);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result?.EnrollmentTerms?.First().Id.Should().Be(1);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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<FileStorageService>(NullLogger<FileStorageService>.Instance);
|
||||
var markdownLoaderLogger = new MyLogger<CourseMarkdownLoader>(NullLogger<CourseMarkdownLoader>.Instance);
|
||||
var markdownSaverLogger = new MyLogger<MarkdownCourseSaver>(NullLogger<MarkdownCourseSaver>.Instance);
|
||||
var otherLogger = NullLoggerFactory.Instance.CreateLogger<FileStorageService>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="akka.testkit" Version="1.5.27.1" />
|
||||
<PackageReference Include="Akka.TestKit.Xunit2" Version="1.5.27.1" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="nsubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.17">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit" Version="2.9.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Management\Management.csproj" />
|
||||
<ProjectReference Include="..\Management.Web\Management.Web.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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<RubricItem>() {
|
||||
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<RubricItem>() { }
|
||||
};
|
||||
|
||||
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<RubricItem>() {
|
||||
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<RubricItem>() {
|
||||
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<RubricItem>() {
|
||||
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<RubricItem>()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
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<RubricItem>() {}
|
||||
};
|
||||
|
||||
var assignmentMarkdown = assignment.ToMarkdown();
|
||||
|
||||
var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown);
|
||||
parsedAssignment.Should().BeEquivalentTo(assignment);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<IWebRequestor> 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<IWebRequestor> 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<IWebRequestor> getTermsMock(EnrollmentTermModel[] expectedTerms)
|
||||
// {
|
||||
// var data = new RedundantEnrollmentTermsResponse(EnrollmentTerms: expectedTerms);
|
||||
// var response = new RestResponse<RedundantEnrollmentTermsResponse>();
|
||||
// response.Data = data;
|
||||
|
||||
// var mockRequestor = new Mock<IWebRequestor>();
|
||||
// mockRequestor
|
||||
// .Setup(s => s.GetAsync<RedundantEnrollmentTermsResponse>(It.IsAny<RestRequest>()))
|
||||
// .ReturnsAsync(response);
|
||||
// return mockRequestor;
|
||||
// }
|
||||
// }
|
||||
@@ -1,3 +0,0 @@
|
||||
global using System.Text.Json;
|
||||
global using FluentAssertions;
|
||||
global using Xunit;
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using OpenTelemetry;
|
||||
|
||||
public class CustomConsoleExporter : BaseExporter<Activity>
|
||||
{
|
||||
public override ExportResult Export(in Batch<Activity> 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;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Management\Management.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Akka" Version="1.5.27.1" />
|
||||
<PackageReference Include="Akka.DependencyInjection" Version="1.5.27.1" />
|
||||
<PackageReference Include="BlazorMonaco" Version="3.2.0" />
|
||||
<PackageReference Include="dotenv.net" Version="3.2.0" />
|
||||
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.8" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>6dc43700-9593-43ca-bda7-4fa2c4e7abc7</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>https</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
<div class="d-flex flex-column p-2 h-100 w-100" style="height: 100%;" >
|
||||
<div>
|
||||
@assignmentContext.Assignment?.Name
|
||||
</div>
|
||||
|
||||
<section class="flex-grow-1 p-1 border rounded-4 bg-dark-subtle" style="min-height: 0;">
|
||||
@if (assignmentContext.Assignment != null)
|
||||
{
|
||||
<AssignmentMarkdownEditor ShowHelp=@showHelp />
|
||||
}
|
||||
</section>
|
||||
|
||||
<div class="d-flex justify-content-end p-3">
|
||||
@if (addingAssignmentToCanvas || deletingAssignmentFromCanvas)
|
||||
{
|
||||
<div>
|
||||
<Spinner />
|
||||
</div>
|
||||
}
|
||||
|
||||
<button class="btn btn-outline-secondary mx-3" @onclick=toggleHelp>
|
||||
Toggle Help
|
||||
</button>
|
||||
<ConfirmationModal Label="Delete" Class="btn btn-danger" OnConfirmAsync="HandleDelete" />
|
||||
<button
|
||||
class="btn btn-outline-secondary mx-3"
|
||||
disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
|
||||
@onclick="addToCanvas"
|
||||
>
|
||||
Add To Canvas
|
||||
</button>
|
||||
@if (assignmentInCanvas != null)
|
||||
{
|
||||
<a
|
||||
class="btn btn-outline-secondary me-1"
|
||||
href="@canvasAssignmentUrl"
|
||||
target="_blank"
|
||||
disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
|
||||
>
|
||||
View in Canvas
|
||||
</a>
|
||||
<button
|
||||
class="btn btn-outline-secondary mx-3"
|
||||
disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
|
||||
@onclick="updateInCanvas"
|
||||
>
|
||||
Update In Canvas
|
||||
</button>
|
||||
<ConfirmationModal
|
||||
Disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
|
||||
Label="Delete from Canvas"
|
||||
Class="btn btn-outline-danger mx-3"
|
||||
OnConfirmAsync="deleteFromCanvas"
|
||||
/>
|
||||
}
|
||||
<button class="btn btn-primary mx-2" @onclick="@(() => {
|
||||
assignmentContext.Assignment = null;
|
||||
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name);
|
||||
})">
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -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<AssignmentFormPage> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<PageTitle>@CourseName - @AssignmentName</PageTitle>
|
||||
|
||||
<div style="height: 100vh;" class="m-0 p-1 d-flex flex-row">
|
||||
@if (loading)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
|
||||
@if (planner.LocalCourse != null && assignmentContext.Assignment != null)
|
||||
{
|
||||
<AssignmentForm />
|
||||
}
|
||||
</div>
|
||||
@@ -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}
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
<div class="d-flex w-100 h-100 flex-row">
|
||||
@if(ShowHelp)
|
||||
{
|
||||
<div class=" rounded rounded-3 bg-black" >
|
||||
<pre class=" me-3 pe-5 ps-3 rounded rounded-3">
|
||||
@HelpText()
|
||||
</pre>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(assignmentContext.Assignment != null && planner.LocalCourse != null)
|
||||
{
|
||||
<div class="row h-100 w-100">
|
||||
<div class="col-6">
|
||||
|
||||
<MonacoTextArea Value=@rawText OnChange=@handleChange />
|
||||
</div>
|
||||
<div class="col-6 overflow-y-auto h-100" >
|
||||
@if (error != null)
|
||||
{
|
||||
<p class="text-danger text-truncate">Error: @error</p>
|
||||
}
|
||||
|
||||
|
||||
<div>Due At: @assignmentContext.Assignment.DueAt</div>
|
||||
<div>Lock At: @assignmentContext.Assignment.LockAt</div>
|
||||
<div>Assignment Group Name @assignmentContext.Assignment.LocalAssignmentGroupName</div>
|
||||
<div>Submission Types</div>
|
||||
<ul>
|
||||
@foreach(var t in assignmentContext.Assignment.SubmissionTypes)
|
||||
{
|
||||
<li>@t</li>
|
||||
}
|
||||
</ul>
|
||||
<hr>
|
||||
<div>
|
||||
@(preview)
|
||||
</div>
|
||||
<hr>
|
||||
<RubricDisplay />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -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)
|
||||
{
|
||||
<div class="row">
|
||||
<h4 class="text-center">Rubric</h4>
|
||||
</div>
|
||||
|
||||
@if (error != null)
|
||||
{
|
||||
<p class="text-danger text-truncate">Error: @error</p>
|
||||
}
|
||||
|
||||
<div class="row border-bottom">
|
||||
<div class="col-6 text-end">Label</div>
|
||||
<div class="col-3 text-center">Points</div>
|
||||
<div class="col-3 text-center">Extra Credit</div>
|
||||
</div>
|
||||
@foreach (var item in assignmentContext?.Assignment?.Rubric ?? [])
|
||||
{
|
||||
<div class="row border-bottom">
|
||||
<div class="col-6 text-end">@item.Label</div>
|
||||
<div class="col-3 text-center">@item.Points</div>
|
||||
<div class="col-3 text-center">@item.IsExtraCredit</div>
|
||||
</div>
|
||||
}
|
||||
<div class="text-end">
|
||||
<div>
|
||||
Required Points: @requiredPoints
|
||||
</div>
|
||||
<div>
|
||||
Extra Credit Points @extraCreditPoints
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -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<string> types { get; set; } = Enumerable.Empty<string>();
|
||||
|
||||
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<string> newTypes)
|
||||
{
|
||||
if(assignmentContext.Assignment != null)
|
||||
{
|
||||
types = newTypes;
|
||||
assignmentContext.SaveAssignment(assignmentContext.Assignment with
|
||||
{
|
||||
SubmissionTypes = types
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<h5>Submission Types</h5>
|
||||
<div class="row" @key="types">
|
||||
|
||||
@foreach (var submissionType in AssignmentSubmissionType.AllTypes)
|
||||
{
|
||||
var isDiscussion = submissionType == AssignmentSubmissionType.DISCUSSION_TOPIC;
|
||||
var allowedToBeChecked = !discussionIsSelected || isDiscussion;
|
||||
|
||||
<div class="col-3">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="@getLabel(submissionType)"
|
||||
checked="@(types.Contains(submissionType) && allowedToBeChecked)"
|
||||
@onchange="(e) => {
|
||||
var isChecked = (bool)(e.Value ?? false);
|
||||
if(isChecked)
|
||||
saveTypes(types.Append(submissionType));
|
||||
else
|
||||
saveTypes(types.Where(t => t != submissionType));
|
||||
}"
|
||||
disabled="@(discussionIsSelected && !isDiscussion)"
|
||||
>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="@getLabel(submissionType)"
|
||||
>
|
||||
@submissionType
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
@page "/test"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject ICanvasService canvas
|
||||
@inject CoursePlanner planner
|
||||
@inject IFileStorageManager fileStorageManager
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -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<ChangeEventArgs> 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<ChangeEventArgs> 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)
|
||||
{
|
||||
<h4 class="text-center">Assignment Groups</h4>
|
||||
@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);
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<label class="form-label">Group Name</label>
|
||||
<input
|
||||
class="form-control"
|
||||
@bind="groupName" @oninput="nameInputCallback">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label class="form-label">Weight</label>
|
||||
<input
|
||||
class="form-control"
|
||||
@bind="weight"
|
||||
@oninput="weightInputCallback"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="d-flex justify-content-end">
|
||||
<button
|
||||
class="btn btn-outline-primary"
|
||||
@onclick="AddAssignmentGroup"
|
||||
>
|
||||
+ Assignment Group
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
@onclick="SyncAssignmentGroupsWithCanvas"
|
||||
disabled="@syncingAssignmentGroups"
|
||||
>
|
||||
Sync Assignment Groups With Canvas
|
||||
</button>
|
||||
@if(syncingAssignmentGroups)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
}
|
||||
@@ -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("/");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<PageTitle>@CourseName</PageTitle>
|
||||
|
||||
|
||||
<div style="height: 100vh;">
|
||||
|
||||
|
||||
@if (loading)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
|
||||
@if (planner.LocalCourse != null)
|
||||
{
|
||||
<div class="pb-3 d-flex justify-content-between" style="height: 4em;">
|
||||
<div class="my-auto">
|
||||
<button @onclick="selectNewCourse" class="btn btn-primary">
|
||||
Select New Course
|
||||
</button>
|
||||
<CourseSettings />
|
||||
<a class="btn btn-outline-secondary" target="_blank"
|
||||
href="@($"{config["CANVAS_URL"]}/courses/{planner.LocalCourse.Settings.CanvasId}")">
|
||||
View In Canvas
|
||||
</a>
|
||||
<div class="my-auto ms-2 d-inline">
|
||||
@planner.LocalCourse.Settings.Name
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (planner.LoadingCanvasData)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
</div>
|
||||
<CourseDetails />
|
||||
}
|
||||
|
||||
</div>
|
||||
@@ -1,54 +0,0 @@
|
||||
@using Management.Web.Course.Module.ModuleItems
|
||||
|
||||
@inject DragContainer dragContainer
|
||||
@inject NavigationManager Navigation
|
||||
@inject AssignmentEditorContext assignmentContext
|
||||
|
||||
@inject MyLogger<AssignmentInDay> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<li
|
||||
draggable="true"
|
||||
@ondragstart="HandleDragStart"
|
||||
@ondragend="HandleDragEnd"
|
||||
@onclick="OnClick"
|
||||
role="button"
|
||||
>
|
||||
@Assignment.Name
|
||||
</li>
|
||||
@@ -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<LocalAssignment> TodaysAssignments
|
||||
{
|
||||
get
|
||||
{
|
||||
if(planner.LocalCourse == null || date == null)
|
||||
return Enumerable.Empty<LocalAssignment>();
|
||||
else
|
||||
return planner.LocalCourse.Modules
|
||||
.SelectMany(m => m.Assignments)
|
||||
.Where(a => a.DueAt.Date == date?.Date);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<LocalQuiz> todaysQuizzes
|
||||
{
|
||||
get
|
||||
{
|
||||
if(planner.LocalCourse == null || date == null)
|
||||
return Enumerable.Empty<LocalQuiz>();
|
||||
else
|
||||
return planner.LocalCourse.Modules
|
||||
.SelectMany(m => m.Quizzes)
|
||||
.Where(q => q.DueAt.Date == date?.Date);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<LocalCoursePage> todaysPages
|
||||
{
|
||||
get
|
||||
{
|
||||
if(planner.LocalCourse == null || date == null)
|
||||
return Enumerable.Empty<LocalCoursePage>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<div
|
||||
class="@calculatedClass"
|
||||
@ondrop="@(() => OnDrop())"
|
||||
@ondragenter="OnDragEnter"
|
||||
@ondragleave="OnDragLeave"
|
||||
ondragover="event.preventDefault();"
|
||||
>
|
||||
@(isWeekDay ? date?.Day : "")
|
||||
<ul class="m-0 ps-3">
|
||||
@foreach (var assignment in TodaysAssignments)
|
||||
{
|
||||
<AssignmentInDay Assignment="assignment" @key="@assignment" />
|
||||
}
|
||||
|
||||
@foreach(var quiz in todaysQuizzes)
|
||||
{
|
||||
<QuizInDay Quiz="quiz" @key="@quiz" />
|
||||
}
|
||||
|
||||
@foreach(var page in todaysPages)
|
||||
{
|
||||
<PageInDay Page="page" @key="page" />
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,55 +0,0 @@
|
||||
@using Management.Web.Course.Module.ModuleItems
|
||||
|
||||
@inject DragContainer dragContainer
|
||||
@inject NavigationManager Navigation
|
||||
@inject PageEditorContext pageContext
|
||||
|
||||
@inject MyLogger<PageInDay> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<li
|
||||
draggable="true"
|
||||
@ondragstart="HandleDragStart"
|
||||
@ondragend="HandleDragEnd"
|
||||
@onclick="OnClick"
|
||||
role="button"
|
||||
>
|
||||
@Page.Name
|
||||
</li>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
<li
|
||||
draggable="true"
|
||||
@ondragstart="HandleDragStart"
|
||||
@ondragend="HandleDragEnd"
|
||||
@onclick="OnClick"
|
||||
role="button"
|
||||
>
|
||||
@Quiz.Name
|
||||
</li>
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
<h3 class="text-center">
|
||||
<a
|
||||
role="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="@("#" + htmlLabel)"
|
||||
aria-expanded="@( isInPast ? "false" : "true")"
|
||||
aria-controls="@htmlLabel"
|
||||
>
|
||||
@MonthName
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<div class="@collapseClass" id="@htmlLabel">
|
||||
<div class="row text-center fw-bold">
|
||||
@foreach (DayOfWeek day in WeekDaysList)
|
||||
{
|
||||
<div class="@(
|
||||
planner.LocalCourse?.Settings.DaysOfWeek.Contains(day) ?? false
|
||||
? "col"
|
||||
: "col text-secondary"
|
||||
)">
|
||||
@day
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@foreach (var week in Month.DaysByWeek)
|
||||
{
|
||||
<div class="row m-3">
|
||||
@foreach (var day in week)
|
||||
{
|
||||
<Day date="day"></Day>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col overflow-y-auto border rounded " style="max-height: 95vh;">
|
||||
@if (planner.LocalCourse != null)
|
||||
{
|
||||
<div class="py-2">
|
||||
|
||||
@foreach (var month in SemesterPlanner.GetMonthsBetweenDates(planner.LocalCourse.Settings.StartDate, planner.LocalCourse.Settings.EndDate))
|
||||
{
|
||||
<MonthDetail Month="month" />
|
||||
<hr />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-4 overflow-y-auto" style="max-height: 95vh;">
|
||||
<Modules />
|
||||
</div>
|
||||
</div>
|
||||
@@ -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<EnrollmentTermModel>? terms { get; set; } = null;
|
||||
private IEnumerable<EnrollmentModel>? 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
@onclick="@(() => modal.Show())"
|
||||
>
|
||||
Edit Course Settings
|
||||
</button>
|
||||
|
||||
<Modal @ref="modal">
|
||||
<Title>
|
||||
<h1>Course Settings</h1>
|
||||
</Title>
|
||||
<Body>
|
||||
|
||||
<h5 class="text-center">Select Days Of Week</h5>
|
||||
<div class="row m-3">
|
||||
@foreach (DayOfWeek day in (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek)))
|
||||
{
|
||||
<div class="col">
|
||||
<button
|
||||
class="@(
|
||||
planner.LocalCourse?.Settings.DaysOfWeek.Contains(day) ?? false
|
||||
? "btn btn-secondary"
|
||||
: "btn btn-outline-secondary"
|
||||
)"
|
||||
@onclick="() =>
|
||||
{
|
||||
if(planner.LocalCourse?.Settings.DaysOfWeek.Contains(day) ?? false)
|
||||
{
|
||||
planner.LocalCourse = planner.LocalCourse with
|
||||
{
|
||||
Settings = planner.LocalCourse.Settings with
|
||||
{
|
||||
DaysOfWeek = planner.LocalCourse.Settings.DaysOfWeek.Where((d) => d != day)
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (planner.LocalCourse != null)
|
||||
{
|
||||
planner.LocalCourse = planner.LocalCourse with
|
||||
{
|
||||
Settings = planner.LocalCourse.Settings with
|
||||
{
|
||||
DaysOfWeek = planner.LocalCourse.Settings.DaysOfWeek.Append(day)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}"
|
||||
>
|
||||
@day
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if(loading)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
|
||||
@if (terms != null)
|
||||
{
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-auto">
|
||||
<form @onsubmit:preventDefault="true">
|
||||
<label for="termselect">Select Term for Start and End Date:</label>
|
||||
<select id="termselect" class="form-select" @bind="selectedTermId">
|
||||
@foreach (var term in terms)
|
||||
{
|
||||
<option value="@term.Id">@term.Name</option>
|
||||
}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@if(planner.LocalCourse != null)
|
||||
{
|
||||
<div class="row justify-content-center m-3 text-center">
|
||||
<div class="col-auto">
|
||||
<div>Default Assignment Due Time</div>
|
||||
<TimePicker Time="planner.LocalCourse.Settings.DefaultDueTime" UpdateTime="@((newTime) =>
|
||||
planner.LocalCourse =
|
||||
planner.LocalCourse with
|
||||
{ Settings = planner.LocalCourse.Settings with { DefaultDueTime=newTime } }
|
||||
)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<AssignmentGroups />
|
||||
|
||||
@if(studentEnrollments != null)
|
||||
{
|
||||
<div>
|
||||
Students to import to github classroom:
|
||||
@foreach(var enrollment in studentEnrollments)
|
||||
{
|
||||
<div class="ps-3">
|
||||
@(enrollment.User.DisplayName ?? enrollment.User.ShortName)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</Body>
|
||||
<Footer>
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
@onclick="@(() => modal.Hide())"
|
||||
>
|
||||
Done Editing Course Settings
|
||||
</button>
|
||||
</Footer>
|
||||
</Modal>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
<div class="@("accordion-item " + (dragging ? "" : ""))" @ondrop="@(() => OnDrop())" @ondragenter="OnDragEnter"
|
||||
@ondragleave="OnDragLeave" ondragover="event.preventDefault();">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="@("#" + accordionId)" aria-controls="@accordionId">
|
||||
<div class="w-100 d-flex justify-content-between pe-3">
|
||||
<div>
|
||||
@Module.Name
|
||||
</div>
|
||||
@if (isSyncedWithCanvas)
|
||||
{
|
||||
<CheckIcon />
|
||||
}
|
||||
else
|
||||
{
|
||||
<SyncIcon />
|
||||
}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
</h2>
|
||||
<div id="@accordionId" class="accordion-collapse collapse">
|
||||
<div class="accordion-body pt-1">
|
||||
<div class="row m-1">
|
||||
<div class="col my-auto">
|
||||
<RenameModule Module="Module" />
|
||||
</div>
|
||||
<div class="col my-auto">
|
||||
@if(publishing)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!isSyncedWithCanvas)
|
||||
{
|
||||
<button
|
||||
class="btn btn-outline-primary"
|
||||
@onclick="Publish"
|
||||
disabled="@publishing"
|
||||
>
|
||||
Add to Canvas
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="col-auto my-auto">
|
||||
<NewPage Module=Module />
|
||||
<NewQuiz Module="Module" />
|
||||
<NewAssignment Module="Module" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<h5>Assignments</h5>
|
||||
|
||||
<div class="row">
|
||||
@* @foreach(var p in Module.Pages)
|
||||
{
|
||||
<PageListItem Page=p />
|
||||
}
|
||||
@foreach (var a in Module.Assignments)
|
||||
{
|
||||
<AssignmentListItem Assignment="a" Module="Module" />
|
||||
}
|
||||
<br>
|
||||
@foreach (var quiz in Module.Quizzes)
|
||||
{
|
||||
<QuizListItem Quiz="quiz" />
|
||||
} *@
|
||||
@foreach(var item in Module.GetSortedModuleItems())
|
||||
{
|
||||
@(item switch
|
||||
{
|
||||
LocalAssignment assignment => (@<AssignmentListItem Assignment="assignment" Module="Module" />),
|
||||
LocalQuiz quiz => (@<QuizListItem Quiz="quiz" />),
|
||||
LocalCoursePage page => (@<PageListItem Page=page />),
|
||||
_ => (@<div></div>)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
<div
|
||||
draggable="true"
|
||||
@ondragstart="HandleDragStart"
|
||||
@ondragend="HandleDragEnd"
|
||||
@onclick="OnClick"
|
||||
role="button"
|
||||
>
|
||||
<ModuleItemLayout Name=@Assignment.Name IsSyncedWithCanvas=@(existsInCanvas && !NeedsToBeUpdatedInCanvas)>
|
||||
@if(
|
||||
planner.LocalCourse != null
|
||||
&& existsInCanvas
|
||||
&& NeedsToBeUpdatedInCanvas
|
||||
&& assignmentInCanvas != null
|
||||
)
|
||||
{
|
||||
<div class="mx-3 text-body-tertiary">
|
||||
@Assignment.GetUpdateReason(
|
||||
(CanvasAssignment)assignmentInCanvas,
|
||||
Assignment.GetCanvasAssignmentGroupId(planner.LocalCourse.Settings.AssignmentGroups))
|
||||
</div>
|
||||
}
|
||||
@if(!existsInCanvas)
|
||||
{
|
||||
<div class="mx-3 text-body-tertiary">
|
||||
no assignment with same name in canvas
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(!showAll)
|
||||
{
|
||||
<div class="card-text overflow-hidden p-2" style="max-height: 5rem;">
|
||||
<div>Points: @Assignment.PointsPossible</div>
|
||||
<div>Due At: @Assignment.DueAt</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card-text">
|
||||
<div class="px-3 py-1 bg-dark-subtle my-1">
|
||||
|
||||
@((MarkupString) @Assignment.GetDescriptionHtml())
|
||||
</div>
|
||||
|
||||
<section class="px-3">
|
||||
<div>Points: @Assignment.PointsPossible</div>
|
||||
<div>Due At: @Assignment.DueAt</div>
|
||||
<div>Lock At: @Assignment.LockAt</div>
|
||||
<div>Submission Types:</div>
|
||||
<ul>
|
||||
@foreach(var type in Assignment.SubmissionTypes)
|
||||
{
|
||||
<li>
|
||||
@type
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
<div
|
||||
class="text-center fs-3 fw-bold lh-1 text-primary"
|
||||
role="button"
|
||||
@onclick:preventDefault="true"
|
||||
@onclick:stopPropagation="true"
|
||||
@onclick="() => showAll = !showAll"
|
||||
>
|
||||
<MeatballsIcon />
|
||||
</div>
|
||||
|
||||
</ModuleItemLayout>
|
||||
</div>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div class="card-title pt-2 px-2 m-0 d-flex justify-content-between">
|
||||
<h4>@Name</h4>
|
||||
@if(IsSyncedWithCanvas)
|
||||
{
|
||||
<CheckIcon />
|
||||
}
|
||||
else
|
||||
{
|
||||
<SyncIcon />
|
||||
}
|
||||
</div>
|
||||
@ChildContent
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
<div
|
||||
draggable="true"
|
||||
@ondragstart="HandleDragStart"
|
||||
@ondragend="HandleDragEnd"
|
||||
@onclick="OnClick"
|
||||
role="button"
|
||||
>
|
||||
<ModuleItemLayout Name=@Page.Name IsSyncedWithCanvas=existsInCanvas>
|
||||
@if(!existsInCanvas)
|
||||
{
|
||||
<div class="mx-3 text-body-tertiary">
|
||||
no page with same name in canvas
|
||||
</div>
|
||||
}
|
||||
<div class="card-text overflow-hidden p-2">
|
||||
<div>Due At: @Page.DueAt</div>
|
||||
</div>
|
||||
</ModuleItemLayout>
|
||||
</div>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
<div
|
||||
draggable="true"
|
||||
@ondragstart="HandleDragStart"
|
||||
@ondragend="HandleDragEnd"
|
||||
@onclick="OnClick"
|
||||
role="button"
|
||||
>
|
||||
<ModuleItemLayout Name=@Quiz.Name IsSyncedWithCanvas=@existsInCanvas>
|
||||
@if(!existsInCanvas)
|
||||
{
|
||||
<div class="mx-3 text-body-tertiary">
|
||||
no quiz with same name in canvas
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="card-text overflow-hidden p-2">
|
||||
<div>Due At: @Quiz.DueAt</div>
|
||||
</div>
|
||||
</ModuleItemLayout>
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
<div class="row justify-content-end mb-1">
|
||||
<div class="col-auto">
|
||||
@if (!showNewModule)
|
||||
{
|
||||
<button class="btn btn-outline-secondary" @onclick="() => showNewModule = true">New Module</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-outline-secondary" @onclick="() => showNewModule = false">Hide New Module</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (showNewModule)
|
||||
{
|
||||
<NewModule OnSubmit="() => showNewModule = false" />
|
||||
}
|
||||
|
||||
@if (planner.LocalCourse != null)
|
||||
{
|
||||
<div class="accordion" id="modulesAccordion">
|
||||
|
||||
@foreach (var module in planner.LocalCourse.Modules)
|
||||
{
|
||||
<ModuleDetail Module="module" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
@onclick="() => modal?.Show()"
|
||||
>
|
||||
+ Assignment
|
||||
</button>
|
||||
|
||||
<Modal @ref="modal">
|
||||
<Title>New Assignment</Title>
|
||||
<Body>
|
||||
<form @onsubmit:preventDefault="true" @onsubmit="submitHandler">
|
||||
<label for="Assignment Name">Name</label>
|
||||
<input id="moduleName" class="form-control" @bind="Name" />
|
||||
</form>
|
||||
<br>
|
||||
<label class="form-label">Assignment Group</label>
|
||||
@if(planner != null)
|
||||
{
|
||||
<ButtonSelect
|
||||
Label="Assignment Group"
|
||||
Options="planner.LocalCourse?.Settings.AssignmentGroups ?? []"
|
||||
GetName="(g) => g?.Name"
|
||||
OnSelect="(g) => setAssignmentGroup(g)"
|
||||
/>
|
||||
}
|
||||
</Body>
|
||||
<Footer>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
@onclick="submitHandler"
|
||||
>
|
||||
Create Assignment
|
||||
</button>
|
||||
</Footer>
|
||||
</Modal>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
<h1>New Module</h1>
|
||||
|
||||
<form @onsubmit:preventDefault="true" @onsubmit="submitHandler">
|
||||
<label for="moduleName">Name:</label>
|
||||
<input id="moduleName" class="form-control" @bind="Name" />
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</form>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
@onclick="() => modal?.Show()"
|
||||
>
|
||||
+ Page
|
||||
</button>
|
||||
|
||||
<Modal @ref="modal">
|
||||
<Title>New Page</Title>
|
||||
<Body>
|
||||
<form @onsubmit:preventDefault="true" @onsubmit="submitHandler">
|
||||
<label for="Page Name">Name</label>
|
||||
<input id="moduleName" class="form-control" @bind="Name" />
|
||||
</form>
|
||||
<br>
|
||||
|
||||
</Body>
|
||||
<Footer>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
@onclick="submitHandler"
|
||||
>
|
||||
Create Page
|
||||
</button>
|
||||
</Footer>
|
||||
</Modal>
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
@onclick="() => modal?.Show()"
|
||||
>
|
||||
+ Quiz
|
||||
</button>
|
||||
|
||||
<Modal @ref="modal">
|
||||
<Title>New Quiz</Title>
|
||||
<Body>
|
||||
<form @onsubmit:preventDefault="true" @onsubmit="submitHandler">
|
||||
<label for="Assignment Name">Name</label>
|
||||
<input id="moduleName" class="form-control" @bind="Name" />
|
||||
</form>
|
||||
<br>
|
||||
<label class="form-label">Assignment Group</label>
|
||||
@if(planner != null && planner.LocalCourse != null)
|
||||
{
|
||||
<ButtonSelect
|
||||
Label="Assignment Group"
|
||||
Options="planner.LocalCourse.Settings.AssignmentGroups"
|
||||
GetName="(g) => g?.Name"
|
||||
OnSelect="(g) => setAssignmentGroup(g)"
|
||||
/>
|
||||
}
|
||||
</Body>
|
||||
<Footer>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@onclick="submitHandler"
|
||||
>
|
||||
CreateQuiz
|
||||
</button>
|
||||
</Footer>
|
||||
</Modal>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
@onclick="() => modal?.Show()"
|
||||
>
|
||||
Rename
|
||||
</button>
|
||||
|
||||
<Modal @ref="modal">
|
||||
<Title>Rename Module</Title>
|
||||
|
||||
<Body>
|
||||
<form @onsubmit:preventDefault="true" @onsubmit="submitHandler">
|
||||
<label for="moduleName">Name</label>
|
||||
<input id="moduleName" class="form-control" @bind="Name" />
|
||||
</form>
|
||||
</Body>
|
||||
<Footer>
|
||||
<button type="button" class="btn btn-primary" @onclick="submitHandler">
|
||||
Rename
|
||||
</button>
|
||||
</Footer>
|
||||
</Modal>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
<div class="d-flex flex-column p-2 h-100 w-100" style="height: 100%;" >
|
||||
<div>
|
||||
@pageContext.Page?.Name
|
||||
</div>
|
||||
|
||||
<section class="flex-grow-1 p-1 border rounded-4 bg-dark-subtle" style="min-height: 0;">
|
||||
@if (pageContext.Page != null)
|
||||
{
|
||||
<CoursePageMarkdownEditor />
|
||||
}
|
||||
</section>
|
||||
|
||||
<div class="d-flex justify-content-end p-3">
|
||||
@if (addingPageToCanvas || deletingPageFromCanvas)
|
||||
{
|
||||
<div>
|
||||
<Spinner />
|
||||
</div>
|
||||
}
|
||||
|
||||
<ConfirmationModal Label="Delete" Class="btn btn-danger" OnConfirmAsync="HandleDelete" />
|
||||
<button
|
||||
class="btn btn-outline-secondary mx-3"
|
||||
disabled="@(addingPageToCanvas || deletingPageFromCanvas)"
|
||||
@onclick="addToCanvas"
|
||||
>
|
||||
Add To Canvas
|
||||
</button>
|
||||
@if (pageInCanvas != null)
|
||||
{
|
||||
<a
|
||||
class="btn btn-outline-secondary me-1"
|
||||
href="@canvasPageUrl"
|
||||
target="_blank"
|
||||
disabled="@(addingPageToCanvas || deletingPageFromCanvas)"
|
||||
>
|
||||
View in Canvas
|
||||
</a>
|
||||
<button
|
||||
class="btn btn-outline-secondary mx-3"
|
||||
disabled="@(addingPageToCanvas || deletingPageFromCanvas)"
|
||||
@onclick="updateInCanvas"
|
||||
>
|
||||
Update In Canvas
|
||||
</button>
|
||||
<ConfirmationModal
|
||||
Disabled="@(addingPageToCanvas || deletingPageFromCanvas)"
|
||||
Label="Delete from Canvas"
|
||||
Class="btn btn-outline-danger mx-3"
|
||||
OnConfirmAsync="deleteFromCanvas"
|
||||
/>
|
||||
}
|
||||
<button class="btn btn-primary mx-2" @onclick="@(() => {
|
||||
pageContext.Page = null;
|
||||
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name);
|
||||
})">
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -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<CoursePageFormPage> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<PageTitle>@CourseName - @PageName</PageTitle>
|
||||
|
||||
<div style="height: 100vh;" class="m-0 p-1 d-flex flex-row">
|
||||
@if (loading)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
|
||||
@if (planner.LocalCourse != null && pageContext.Page != null)
|
||||
{
|
||||
<CoursePageForm />
|
||||
}
|
||||
</div>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
<div class="d-flex w-100 h-100 flex-row">
|
||||
@if(pageContext.Page != null && planner.LocalCourse != null)
|
||||
{
|
||||
<div class="row h-100 w-100">
|
||||
<div class="col-6">
|
||||
|
||||
<MonacoTextArea Value=@rawText OnChange=@handleChange />
|
||||
</div>
|
||||
<div class="col-6 overflow-y-auto h-100" >
|
||||
@if (error != null)
|
||||
{
|
||||
<p class="text-danger text-truncate">Error: @error</p>
|
||||
}
|
||||
<div>Due At: @pageContext.Page.DueAt</div>
|
||||
<hr>
|
||||
<div>
|
||||
@(preview)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -1,42 +0,0 @@
|
||||
@page
|
||||
@model Management.Web.Pages.ErrorModel
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>Error</title>
|
||||
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main">
|
||||
<div class="content px-4">
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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<ErrorModel> _logger;
|
||||
|
||||
public ErrorModel(ILogger<ErrorModel> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
<PageTitle>Index</PageTitle>
|
||||
|
||||
<br>
|
||||
@if(planner.LocalCourse == null)
|
||||
{
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-auto">
|
||||
<CurrentFiles RefreshKey="refreshKey" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(!showNewFile)
|
||||
{
|
||||
<div class="text-center">
|
||||
<button
|
||||
@onclick="@(()=>showNewFile = true)"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
Mange New Course
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(showNewFile)
|
||||
{
|
||||
<div class="text-center">
|
||||
<button
|
||||
@onclick="@(()=>showNewFile = false)"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
Hide File Initialization
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="border rounded bg-dark-subtle p-3 my-3">
|
||||
<InitializeNewCourse NewFileCreated="NewFileCreated" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<br>
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired]
|
||||
public LocalQuizQuestion Question { get; set; } = default!;
|
||||
|
||||
}
|
||||
|
||||
<div class="row justify-content-between text-secondary">
|
||||
<div class="col">
|
||||
points: @Question.Points
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
@Question.QuestionType
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@((MarkupString)Question.HtmlText)
|
||||
|
||||
@if(Question.QuestionType == QuestionType.MATCHING)
|
||||
{
|
||||
@foreach(var answer in Question.Answers)
|
||||
{
|
||||
<div class="mx-3 mb-1 bg-dark px-2 rounded rounded-2 border row">
|
||||
<div
|
||||
class="col text-end my-auto p-1"
|
||||
>
|
||||
@answer.Text
|
||||
</div>
|
||||
<div
|
||||
class="col my-auto"
|
||||
>
|
||||
@answer.MatchedText
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach(var answer in Question.Answers)
|
||||
{
|
||||
string answerPreview = answer.HtmlText.StartsWith("<p>")
|
||||
? answer.HtmlText.Replace("<p>", "<p class='m-0'>")
|
||||
: answer.HtmlText;
|
||||
|
||||
<div class="mx-3 mb-1 bg-dark px-2 rounded rounded-2 d-flex flex-row border">
|
||||
@if(answer.Correct)
|
||||
{
|
||||
<svg
|
||||
style="width: 1em;"
|
||||
class="me-1 my-auto"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M4 12.6111L8.92308 17.5L20 6.5"
|
||||
stroke="var(--bs-success)"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div
|
||||
class="me-1 my-auto"
|
||||
style="width: 1em;"
|
||||
>
|
||||
@if(Question.QuestionType == QuestionType.MULTIPLE_ANSWERS)
|
||||
{
|
||||
<span>[ ]</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="markdownQuizAnswerPreview p-1">
|
||||
@((MarkupString)answerPreview)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
<div class="d-flex flex-column h-100">
|
||||
<div class="d-flex flex-row h-100 p-2">
|
||||
<div class="row flex-grow-1">
|
||||
<div class="col-6">
|
||||
<MonacoTextArea
|
||||
Value="@quizMarkdownInput"
|
||||
OnChange="@((v) => quizMarkdownInput = v)"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div class="col-6 h-100 overflow-y-auto">
|
||||
@if (error != null)
|
||||
{
|
||||
<p class="text-danger text-truncate">Error: @error</p>
|
||||
}
|
||||
@if(testQuiz != null)
|
||||
{
|
||||
<QuizPreview Quiz="testQuiz" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -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<QuizFormPage> 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
|
||||
";
|
||||
}
|
||||
|
||||
<div class="d-flex flex-column py-3" style="height: 100vh;">
|
||||
|
||||
<section>
|
||||
<div class="row justify-content-between">
|
||||
<div class="col-auto my-auto">
|
||||
<button class="btn btn-outline-secondary" @onclick="done">
|
||||
← go back
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto my-auto">
|
||||
<h2>
|
||||
@quizContext.Quiz?.Name
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@if (quizContext.Quiz == null)
|
||||
{
|
||||
<div class="col-auto">
|
||||
<Spinner />
|
||||
</div>
|
||||
}
|
||||
<div class="col-auto me-3">
|
||||
<h3>
|
||||
Questions: @quizContext.Quiz?.Questions.Count() - Points: @quizPoints
|
||||
</h3>
|
||||
@if (quizInCanvas != null)
|
||||
{
|
||||
@if (quizInCanvas?.Published == true)
|
||||
{
|
||||
<div class="text-success">
|
||||
Published!
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-danger">
|
||||
Not Published
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="flex-grow-1 w-100 d-flex justify-content-center border rounded-4 bg-dark-subtle"
|
||||
style="min-height: 10%; max-width: 100%;"
|
||||
>
|
||||
|
||||
@if(showHelp)
|
||||
{
|
||||
<pre class="bg-dark-subtle me-3 pe-5 ps-3 rounded rounded-3">
|
||||
@exampleMarkdownQuestion
|
||||
</pre>
|
||||
}
|
||||
<div class="w-100" style="max-width: 120em; max-height: 100%;">
|
||||
@if (quizContext.Quiz != null)
|
||||
{
|
||||
<MarkdownQuizForm />
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-outline-secondary mt-3"
|
||||
@onclick="@(() => showHelp = !showHelp)"
|
||||
>
|
||||
toggle help
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section class="p-2">
|
||||
@if (quizContext.Quiz != null)
|
||||
{
|
||||
<div class="row justify-content-end">
|
||||
<div class="col-auto">
|
||||
<ConfirmationModal
|
||||
Label="Delete"
|
||||
Class="btn btn-danger"
|
||||
OnConfirm="deleteQuiz"
|
||||
Disabled="@addingQuizToCanvas"
|
||||
/>
|
||||
<button class="btn btn-outline-secondary me-1" @onclick="addToCanvas" disabled="@addingQuizToCanvas">
|
||||
Add to Canvas
|
||||
</button>
|
||||
@if (quizInCanvas != null)
|
||||
{
|
||||
<a class="btn btn-outline-secondary me-1" href="@canvasQuizUrl" target="_blank">
|
||||
View in Canvas
|
||||
</a>
|
||||
}
|
||||
<button class="btn btn-primary" @onclick="done" disabled="@addingQuizToCanvas">
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (addingQuizToCanvas)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
@@ -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)
|
||||
{
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-auto" style="min-width: 35em;">
|
||||
<div class="row">
|
||||
<div class="col-6 text-end">Name: </div>
|
||||
<div class="col-6">@Quiz.Name</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 text-end">Due At: </div>
|
||||
<div class="col-6">@Quiz.DueAt</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 text-end">Lock At: </div>
|
||||
<div class="col-6">@Quiz.LockAt</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 text-end">Shuffle Answers: </div>
|
||||
<div class="col-6">@Quiz.ShuffleAnswers</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 text-end">Allowed Attempts: </div>
|
||||
<div class="col-6">@Quiz.AllowedAttempts</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 text-end">One question at a time: </div>
|
||||
<div class="col-6">@Quiz.OneQuestionAtATime</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 text-end">Assignment Group: </div>
|
||||
<div class="col-6">@Quiz.LocalAssignmentGroupName</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-3" style="white-space: pre-wrap;">@Quiz.Description</div>
|
||||
|
||||
@foreach(var question in Quiz.Questions)
|
||||
{
|
||||
<div class="bg-dark-subtle mt-1 p-1 ps-2 rounded rounded-2">
|
||||
<MarkdownQuestionPreview
|
||||
Question="question"
|
||||
@key="question"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
@page "/"
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@namespace Management.Web.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="~/" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
|
||||
crossorigin="anonymous"></script>
|
||||
<link href="css/site.css" rel="stylesheet" />
|
||||
<link href="Management.Web.styles.css" rel="stylesheet" />
|
||||
|
||||
|
||||
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Kanit&family=Mukta&family=Roboto&family=Sofia+Sans+Condensed:wght@400;500&display=swap"
|
||||
rel="stylesheet">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,600&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
|
||||
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
|
||||
</head>
|
||||
|
||||
<body data-bs-theme="dark">
|
||||
<component type="typeof(App)" render-mode="ServerPrerendered" />
|
||||
|
||||
<div id="blazor-error-ui" class="p-0 m-0">
|
||||
<environment include="Staging,Production">
|
||||
An error has occurred. This application may no longer respond until reloaded.
|
||||
</environment>
|
||||
<environment include="Development">
|
||||
An unhandled exception has occurred. See browser dev tools for details.
|
||||
</environment>
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<script src="_content/BlazorMonaco/jsInterop.js"></script>
|
||||
<script src="_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
|
||||
<script src="_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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<IWebRequestor, WebRequestor>();
|
||||
builder.Services.AddSingleton<CanvasServiceUtils>();
|
||||
builder.Services.AddSingleton<ICanvasAssignmentService, CanvasAssignmentService>();
|
||||
builder.Services.AddSingleton<ICanvasCoursePageService, CanvasCoursePageService>();
|
||||
builder.Services.AddSingleton<ICanvasAssignmentGroupService, CanvasAssignmentGroupService>();
|
||||
builder.Services.AddSingleton<ICanvasQuizService, CanvasQuizService>();
|
||||
builder.Services.AddSingleton<ICanvasModuleService, CanvasModuleService>();
|
||||
builder.Services.AddSingleton<ICanvasService, CanvasService>();
|
||||
|
||||
builder.Services.AddSingleton<MarkdownCourseSaver>();
|
||||
builder.Services.AddSingleton<CourseMarkdownLoader>();
|
||||
|
||||
builder.Services.AddSingleton<FileStorageService>();
|
||||
|
||||
// one actor system, maybe different actor for different pages?
|
||||
builder.Services.AddSingleton<AkkaService>();
|
||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<AkkaService>());
|
||||
|
||||
|
||||
// TODO: need to handle scoped requirements
|
||||
// builder.Services.AddSingleton(sp =>
|
||||
// {
|
||||
// var akka = sp.GetRequiredService<AkkaService>();
|
||||
// return new CanvasQueueActorWrapper(akka.CoursePlannerActor ?? throw new Exception("Canvas queue actor not properly created"));
|
||||
// });
|
||||
builder.Services.AddSingleton<IFileStorageManager>(sp =>
|
||||
{
|
||||
var akka = sp.GetRequiredService<AkkaService>();
|
||||
return new LocalStorageActorWrapper(akka.StorageActor ?? throw new Exception("Canvas queue actor not properly created"));
|
||||
});
|
||||
|
||||
|
||||
builder.Services.AddScoped<CoursePlanner>();
|
||||
builder.Services.AddScoped<AssignmentEditorContext>();
|
||||
builder.Services.AddScoped<PageEditorContext>();
|
||||
builder.Services.AddScoped<QuizEditorContext>();
|
||||
builder.Services.AddScoped<DragContainer>();
|
||||
|
||||
builder.Services.AddSingleton<FileConfiguration>();
|
||||
|
||||
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<IServer>()?.Features.Get<IServerAddressesFeature>()?.Addresses ?? [];
|
||||
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
Console.WriteLine("Running at: " + address);
|
||||
}
|
||||
|
||||
app.WaitForShutdown();
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<svg
|
||||
width="24px"
|
||||
height="24px"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke="var(--bs-success-border-subtle)"
|
||||
d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
stroke="var(--bs-success-border-subtle)"
|
||||
d="M9 12L10.6828 13.6828V13.6828C10.858 13.858 11.142 13.858 11.3172 13.6828V13.6828L15 10"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 561 B |
@@ -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<Task>? OnConfirmAsync { get; init; }
|
||||
[Parameter]
|
||||
public Func<Task>? 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();
|
||||
}
|
||||
}
|
||||
|
||||
<button
|
||||
class="@(Class != "" ? Class : "btn btn-danger ")"
|
||||
@onclick="() => modal?.Show()"
|
||||
disabled="@Disabled"
|
||||
>
|
||||
@Label
|
||||
</button>
|
||||
|
||||
<Modal @ref="modal">
|
||||
<Title>Are you sure you want to @Label?</Title>
|
||||
<Body>
|
||||
<div class="text-center">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@onclick="HandleDeny"
|
||||
disabled="@Disabled"
|
||||
>
|
||||
no
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@onclick="HandleConfirm"
|
||||
disabled="@Disabled"
|
||||
>
|
||||
yes
|
||||
</button>
|
||||
</div>
|
||||
</Body>
|
||||
<Footer>
|
||||
@if(doingAsyncThings)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
</Footer>
|
||||
</Modal>
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
@typeparam T
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired]
|
||||
public string Label { get; set; } = string.Empty;
|
||||
[Parameter, EditorRequired]
|
||||
public IEnumerable<T> Options { get; set; } = default!;
|
||||
[Parameter, EditorRequired]
|
||||
public Func<T?, string?> GetName { get; set; } = default!;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public Action<T?> 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}";
|
||||
}
|
||||
}
|
||||
|
||||
<div key="@GetName(SelectedOption)">
|
||||
@foreach(var option in Options)
|
||||
{
|
||||
<button
|
||||
class="@getButtonClass(option)"
|
||||
@onclick="() => onSelect(option)"
|
||||
>
|
||||
@GetName(option)
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
@typeparam T
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired]
|
||||
public string Label { get; set; } = string.Empty;
|
||||
[Parameter, EditorRequired]
|
||||
public IEnumerable<T> Options { get; set; } = default!;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public Func<T, string> GetId { get; set; } = default!;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public Func<T, string> GetName { get; set; } = default!;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public Action<T?> 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);
|
||||
}
|
||||
}
|
||||
|
||||
<div>
|
||||
<label for="@htmlLabel">@Label</label>
|
||||
<select
|
||||
id="@htmlLabel"
|
||||
name="@htmlLabel"
|
||||
@oninput="onSelect"
|
||||
>
|
||||
@foreach(var option in Options)
|
||||
{
|
||||
<option
|
||||
value="@(GetId(option))"
|
||||
>
|
||||
@GetName(option)
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 10a2 2 0 11-4.001-.001A2 2 0 016 10zm6 0a2 2 0 11-4.001-.001A2 2 0 0112 10zm6 0a2 2 0 11-4.001-.001A2 2 0 0118 10z"
|
||||
fill="var(--bs-primary)"
|
||||
/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 268 B |
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
<div class="modal @modalClass" @onmousedown="Hide">
|
||||
<div class="@($"modal-dialog modal-{Size}")" role="document" @onmousedown:stopPropagation="true">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title text-center w-100">@Title</h4>
|
||||
<button type="button" class="btn-close" @onclick="Hide"></button>
|
||||
</div>
|
||||
<div class="modal-body">@Body</div>
|
||||
<div class="modal-footer">@Footer</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (showBackdrop)
|
||||
{
|
||||
<div
|
||||
class="modal-backdrop fade show"
|
||||
></div>
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
.show-modal {
|
||||
animation: enter 250ms;
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes enter {
|
||||
from {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
|
||||
@using BlazorMonaco
|
||||
@using BlazorMonaco.Editor
|
||||
|
||||
<h3>Code Editor</h3>
|
||||
|
||||
<div>
|
||||
<div style="margin:10px 0;">
|
||||
New Value: <input type="text" @bind="_valueToSet" style="width: 400px;" /> <button @onclick="SetValue">Set Value</button>
|
||||
</div>
|
||||
<div style="margin:10px 0;">
|
||||
<button @onclick="GetValue">Get Value</button>
|
||||
</div>
|
||||
<div style="margin:10px 0;">
|
||||
See the console for results.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="height: 300px"
|
||||
>
|
||||
<StandaloneCodeEditor
|
||||
@ref="_editor"
|
||||
Id="sample-code-editor-123"
|
||||
ConstructionOptions="EditorConstructionOptions"
|
||||
OnDidInit="EditorOnDidInit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@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}");
|
||||
}
|
||||
}
|
||||
@@ -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<string> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<StandaloneCodeEditor @ref="_editor" Id="@randomId" ConstructionOptions="EditorConstructionOptions"
|
||||
OnDidChangeModelContent="OnDidChangeModelContent" />
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
<div class="text-center m-3">
|
||||
<span class="loader"></span>
|
||||
</div>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<svg
|
||||
class="d-inline"
|
||||
width="24px"
|
||||
height="24px"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_429_11068)">
|
||||
<path
|
||||
stroke="var(--bs-danger-border-subtle)"
|
||||
d="M3 11.9998C3 7.02925 7.02944 2.99982 12 2.99982C14.8273 2.99982 17.35 4.30348 19 6.34248"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<path
|
||||
stroke="var(--bs-danger-border-subtle)"
|
||||
d="M19.5 2.99982L19.5 6.99982L15.5 6.99982"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<path
|
||||
stroke="var(--bs-danger-border-subtle)"
|
||||
d="M21 11.9998C21 16.9704 16.9706 20.9998 12 20.9998C9.17273 20.9998 6.64996 19.6962 5 17.6572"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<path
|
||||
stroke="var(--bs-danger-border-subtle)"
|
||||
d="M4.5 20.9998L4.5 16.9998L8.5 16.9998"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_429_11068">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,127 +0,0 @@
|
||||
|
||||
@inject CoursePlanner planner
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public SimpleTimeOnly Time { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public Action<SimpleTimeOnly> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
<div>
|
||||
<select
|
||||
@onchange="async (e) =>
|
||||
UpdateTime(
|
||||
new SimpleTimeOnly
|
||||
{
|
||||
Hour=convertTo24Hour(Convert.ToInt32(e.Value), AmPm),
|
||||
Minute=Time.Minute
|
||||
}
|
||||
)"
|
||||
class="form-control w-auto d-inline"
|
||||
>
|
||||
<option
|
||||
value="12"
|
||||
selected="@(12 == AdjustedHour)"
|
||||
>
|
||||
12
|
||||
</option>
|
||||
@foreach (var hour in Enumerable.Range(1, 11))
|
||||
{
|
||||
<option
|
||||
value="@hour"
|
||||
selected="@(hour == AdjustedHour)"
|
||||
>
|
||||
@hour.ToString("00")
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
<span class="pl-0">:</span>
|
||||
<select
|
||||
@onchange="async (e) =>
|
||||
UpdateTime(
|
||||
new SimpleTimeOnly
|
||||
{
|
||||
Hour=Time.Hour,
|
||||
Minute=Convert.ToInt32(e.Value)
|
||||
}
|
||||
)"
|
||||
class="form-control w-auto d-inline"
|
||||
>
|
||||
@foreach (var minute in new int[] {0, 15, 30, 45, 59})
|
||||
{
|
||||
<option
|
||||
value="@minute"
|
||||
selected="@(minute == Time.Minute)"
|
||||
>
|
||||
@(minute.ToString("00"))
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
<select
|
||||
@onchange="(e) =>
|
||||
UpdateTime(
|
||||
new SimpleTimeOnly
|
||||
{
|
||||
Hour=convertTo24Hour(Time.Hour, e.Value?.ToString()),
|
||||
Minute=Time.Minute
|
||||
}
|
||||
)"
|
||||
class="form-control w-auto d-inline"
|
||||
>
|
||||
@foreach (var amPm in new string[] {"AM", "PM"})
|
||||
{
|
||||
<option
|
||||
value="@amPm"
|
||||
selected="@(amPm == AmPm)"
|
||||
>
|
||||
@amPm
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@@ -1,49 +0,0 @@
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter, EditorRequired]
|
||||
public Func<string,Task> 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
<Modal @ref="modal">
|
||||
<Title>
|
||||
<h3>Canvas Token</h3>
|
||||
</Title>
|
||||
<Body>
|
||||
<div>
|
||||
<p>
|
||||
Please input your canvas token to enable canvas integration
|
||||
</p>
|
||||
<p>
|
||||
We only store the token encrypted in your browser. We do not store the token on our servers.
|
||||
</p>
|
||||
<p>
|
||||
You can get your canvas token <a href="https://snow.instructure.com/profile/settings">here</a>
|
||||
</p>
|
||||
<form
|
||||
onsubmit:preventDefault="true"
|
||||
@onsubmit="async () => await SetToken(tokenInput)"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
@bind="tokenInput"
|
||||
@bind:event="oninput"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</Body>
|
||||
<Footer>
|
||||
</Footer>
|
||||
</Modal>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
@using LocalModels
|
||||
|
||||
@inject IFileStorageManager fileStorageManager
|
||||
@inject CoursePlanner planner
|
||||
@inject NavigationManager Navigation
|
||||
@inject MyLogger<CurrentFiles> logger
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public int RefreshKey { get; set; }
|
||||
public IEnumerable<LocalCourse>? 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);
|
||||
}
|
||||
}
|
||||
|
||||
<div class="">
|
||||
@if (localCourses != null)
|
||||
{
|
||||
<h3 class="text-center mb-3">Stored Courses</h3>
|
||||
@foreach (var course in localCourses)
|
||||
{
|
||||
var location = "/course/" + course.Settings.Name;
|
||||
<div>
|
||||
<div class=" fs-4 text-start mb-3 hover-underline-animation" @onclick="(e) => handleClick(e, course)" role='button'>
|
||||
@course.Settings.Name
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<LocalCourse>? localCourses { get; set; }
|
||||
private IEnumerable<EnrollmentTermModel>? terms { get; set; } = null;
|
||||
private IEnumerable<CourseModel>? 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<DayOfWeek> days { get; set; } = new();
|
||||
|
||||
private IEnumerable<string> 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)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
|
||||
@if (terms != null)
|
||||
{
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-auto">
|
||||
<label for="termselect">Select Term:</label>
|
||||
<select
|
||||
id="termselect"
|
||||
class="form-select"
|
||||
@bind="selectedTermId"
|
||||
>
|
||||
@foreach (var term in terms)
|
||||
{
|
||||
<option value="@term.Id">@term.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@if (selectedTerm is not null)
|
||||
{
|
||||
@if (loadingCourses)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
|
||||
@if (courses != null)
|
||||
{
|
||||
<div class="row justify-content-center m-3">
|
||||
<div class="col-auto">
|
||||
<label for="courseselect">Select Course:</label>
|
||||
<select id="courseselect" class="form-select" @bind="selectedCourseId">
|
||||
@foreach (var course in courses)
|
||||
{
|
||||
<option value="@course.Id">@course.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center m-3">
|
||||
<div class="col-auto">
|
||||
<label for="directorySelect">Select Storage Directory:</label>
|
||||
<select
|
||||
id="directorySelect"
|
||||
class="form-select"
|
||||
@bind="selectedStorageDirectory"
|
||||
>
|
||||
<option></option>
|
||||
@foreach (var folder in directoriesNotUsed)
|
||||
{
|
||||
<option value="@folder">@folder</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h5 class="text-center mt-3">Select Days Of Week</h5>
|
||||
<div class="row m-3">
|
||||
@foreach (DayOfWeek day in (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek)))
|
||||
{
|
||||
<div class="col">
|
||||
<button
|
||||
class="@(
|
||||
days.Contains(day)
|
||||
? "btn btn-secondary"
|
||||
: "btn btn-outline-secondary"
|
||||
)"
|
||||
@onclick="() => {
|
||||
if(days.Contains(day))
|
||||
days.Remove(day);
|
||||
else
|
||||
days.Add(day);
|
||||
}"
|
||||
>
|
||||
@day
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="text-center">
|
||||
<button @onclick="SaveNewCourse" class="btn btn-primary">
|
||||
Create Course Files
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<PageTitle>Management.Web</PageTitle>
|
||||
|
||||
|
||||
<main class="d-flex justify-content-center">
|
||||
<div class="w-100 px-3">
|
||||
@Body
|
||||
</div>
|
||||
</main>
|
||||
@@ -1,4 +0,0 @@
|
||||
public class DragContainer
|
||||
{
|
||||
public Action<DateTime?, LocalModule?>? DropCallback { get; set; }
|
||||
}
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user