explorak5_canvas_login 3.5.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/explorak5_canvas_login/app/views/dashboard/_dashboard_card.html.erb +29 -0
- data/lib/explorak5_canvas_login/app/views/layout/_application_content.html.erb +292 -0
- data/lib/explorak5_canvas_login/version.rb +1 -1
- metadata +2 -2
- data/lib/explorak5_canvas_login/ui/dashboard-card/react/DashboardCard.tsx +0 -372
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '028890a661159f6d765467d08b981e11ee18c470e973ec26f6021115484f503a'
|
4
|
+
data.tar.gz: 3e0d52912eb3a84acdbdd61978883875dd64f4d81e1353e5295f40e984f13d47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb3ef80fa1284a077a6868cdc470bfc8522587874825f59e1956100248581396a41e8ab1369a0ef1e09aee5a2a7e616a4e1bc35bf1d766800294f31c32fc5eec
|
7
|
+
data.tar.gz: '098556a2b1d6e65871a4a394c7a303691a4ce9b7b441b6978e1b95903f3a929ca22ff9f553017990eab01722a05126663f13ed2195b061322d29ff1dfd10739b'
|
@@ -117,6 +117,35 @@
|
|
117
117
|
margin: 0;
|
118
118
|
box-shadow: 0 0 1px rgb(0 0 0 / 8%), 0 1px 6px rgb(0 0 0 / 8%), 0 5px 5px rgb(0 0 0 /8%);
|
119
119
|
}
|
120
|
+
.img-logout{
|
121
|
+
height: 50px !important;
|
122
|
+
width: 50px !important;
|
123
|
+
}
|
124
|
+
.avatar-img {
|
125
|
+
height: 100% !important;
|
126
|
+
width: 50px !important;
|
127
|
+
}
|
128
|
+
|
129
|
+
.avatar-img{
|
130
|
+
width: 100px !important;
|
131
|
+
height: 100% !important
|
132
|
+
}
|
133
|
+
.ic-avatar {
|
134
|
+
margin-right: 50px;
|
135
|
+
border: 2px solid #3d88d9 !important;
|
136
|
+
width: 70px !important;
|
137
|
+
height: 70px !important;
|
138
|
+
box-sizing: border-box;
|
139
|
+
display: inline-block;
|
140
|
+
vertical-align: middle;
|
141
|
+
}
|
142
|
+
.ic-Dashboard-header__layout {
|
143
|
+
border-bottom: unset !important;
|
144
|
+
color: #3d88d9
|
145
|
+
}
|
146
|
+
.unpublished_courses_redesign {
|
147
|
+
color: #3d88d9
|
148
|
+
}
|
120
149
|
</style>
|
121
150
|
<%
|
122
151
|
# Copyright (C) 2015 - present Instructure, Inc.
|
@@ -0,0 +1,292 @@
|
|
1
|
+
<%
|
2
|
+
# Copyright (C) 2011 - present Instructure, Inc.
|
3
|
+
#
|
4
|
+
# This file is part of Canvas.
|
5
|
+
#
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
8
|
+
# Software Foundation, version 3 of the License.
|
9
|
+
#
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
13
|
+
# details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
%>
|
18
|
+
<%-
|
19
|
+
css_bundle(:instructure_eportfolio) if @eportfolio_view === true
|
20
|
+
css_bundle(:new_user_tutorials) if tutorials_enabled?
|
21
|
+
js_bundle(:navigation_header) unless @headers == false
|
22
|
+
|
23
|
+
if @domain_root_account&.feature_enabled?(:top_navigation_placement)
|
24
|
+
js_env INIT_DRAWER_LAYOUT_MUTEX: "init-drawer-layout"
|
25
|
+
js_bundle(:top_navigation_tools)
|
26
|
+
end
|
27
|
+
|
28
|
+
load_blueprint_courses_ui
|
29
|
+
@has_content_notices = load_content_notices
|
30
|
+
provide :head, include_common_stylesheets
|
31
|
+
set_badge_counts_for(@context, @current_user) if @set_badge_counts
|
32
|
+
js_env notices: flash_notices()
|
33
|
+
-%>
|
34
|
+
<%= render :partial => "layouts/head" %>
|
35
|
+
<%-
|
36
|
+
left_side = nil
|
37
|
+
left_side_custom = nil
|
38
|
+
right_side = (yield :right_side).presence
|
39
|
+
|
40
|
+
# Collapse the course menu according to user preference, unless a certain action
|
41
|
+
# requests that it default to collapsed for that page
|
42
|
+
@collapse_course_menu ||= @current_user&.reload&.collapse_course_nav?
|
43
|
+
context_is_course_or_account = @context&.is_a?(Course) || @context&.is_a?(Account)
|
44
|
+
@enhanced_rubrics_enabled = context_is_course_or_account ? @context.feature_enabled?(:enhanced_rubrics) : false
|
45
|
+
|
46
|
+
if @collapse_course_menu
|
47
|
+
subnav_menu_text = t('Show Navigation Menu')
|
48
|
+
subnav_menu_text = t('Show Courses Navigation Menu') if active_path?('/courses')
|
49
|
+
subnav_menu_text = t('Show Account Navigation Menu') if active_path?('/profile')
|
50
|
+
subnav_menu_text = t('Show Admin Navigation Menu') if active_path?('/accounts')
|
51
|
+
subnav_menu_text = t('Show Groups Navigation Menu') if active_path?('/groups')
|
52
|
+
else
|
53
|
+
subnav_menu_text = t('Hide Navigation Menu')
|
54
|
+
subnav_menu_text = t('Hide Courses Navigation Menu') if active_path?('/courses')
|
55
|
+
subnav_menu_text = t('Hide Account Navigation Menu') if active_path?('/profile')
|
56
|
+
subnav_menu_text = t('Hide Admin Navigation Menu') if active_path?('/accounts')
|
57
|
+
subnav_menu_text = t('Hide Groups Navigation Menu') if active_path?('/groups')
|
58
|
+
end
|
59
|
+
|
60
|
+
@collapse_global_nav = @current_user.try(:collapse_global_nav?)
|
61
|
+
@body_class_no_headers = @headers == false
|
62
|
+
@show_embedded_chat = embedded_chat_visible
|
63
|
+
@show_fixed_bottom = (@fixed_warnings.present? || (@real_current_user && @real_current_user != @current_user)) && (!@body_class_no_headers || @outer_frame)
|
64
|
+
body_classes << "no-headers" if @body_class_no_headers
|
65
|
+
unless (body_classes.include? "no-headers") || (@show_left_side == false)
|
66
|
+
left_side = nil
|
67
|
+
skip_for_streaming :left_side, except: ["eportfolios/show", "eportfolio_categories/show", "eportfolio_entries/show"] do
|
68
|
+
left_side = (yield :left_side).presence
|
69
|
+
end
|
70
|
+
@show_left_side ||= (section_tabs.length > 0)
|
71
|
+
end
|
72
|
+
body_classes << "with-left-side" if @show_left_side
|
73
|
+
body_classes << "course-menu-expanded" if body_classes.include?("with-left-side") && !@collapse_course_menu
|
74
|
+
#we dont want to render a right side unless there is actually content in it.
|
75
|
+
body_classes << "with-right-side" if right_side and not right_side.strip.empty?
|
76
|
+
body_classes << "padless-content" if @padless
|
77
|
+
body_classes << "with-embedded-chat" if @show_embedded_chat
|
78
|
+
body_classes << 'with-fixed-bottom' if @show_fixed_bottom
|
79
|
+
body_classes << 'pages' if controller.js_env[:WIKI_PAGE].present?
|
80
|
+
body_classes << get_active_tab
|
81
|
+
body_classes << 'Underline-All-Links__enabled' if @current_user && @current_user.feature_enabled?(:underline_all_links)
|
82
|
+
body_classes << 'is-masquerading-or-student-view' if @real_current_user && @real_current_user != @current_user
|
83
|
+
body_classes << 'primary-nav-expanded' unless @collapse_global_nav
|
84
|
+
body_classes << 'primary-nav-transitions' if @collapse_global_nav
|
85
|
+
# We probably want to consider doing this everywhere, all the time, but when I did
|
86
|
+
# for LS-1745, people complained a lot, so maybe not.
|
87
|
+
body_classes << 'full-width' if @domain_root_account.try(:feature_enabled?, :new_user_tutorial)
|
88
|
+
body_classes << "context-#{@context.asset_string}" if @context
|
89
|
+
body_classes << "responsive_student_grades_page" if !!@domain_root_account&.feature_enabled?(:responsive_student_grades_page)
|
90
|
+
-%>
|
91
|
+
<body class="<%= (body_classes).uniq.join(" ") %>">
|
92
|
+
<%if @current_user && @real_current_user && @real_current_user != @current_user %>
|
93
|
+
<div role="alert" class="screenreader-only">
|
94
|
+
<% if @current_user.fake_student? %>
|
95
|
+
<%= t("You are currently logged into Student View") %>
|
96
|
+
<% else %>
|
97
|
+
<%= t("You are currently acting as %{user_name}", :user_name => @current_user.short_name) %>
|
98
|
+
<% end %>
|
99
|
+
</div>
|
100
|
+
<% end %>
|
101
|
+
<%# Flash messages must be outside of #application or they won't work in screenreaders with modals open. %>
|
102
|
+
<%= render :partial => 'shared/static_notices' %>
|
103
|
+
<%= render :partial => 'shared/flash_notices' %>
|
104
|
+
<%if @domain_root_account&.feature_enabled?(:top_navigation_placement) %>
|
105
|
+
<div id="drawer-layout-mount-point"></div>
|
106
|
+
<% end %>
|
107
|
+
<div id="application" class="ic-app">
|
108
|
+
<%= render(:partial => 'shared/new_nav_header') unless @headers == false %>
|
109
|
+
|
110
|
+
<div id="instructure_ajax_error_box">
|
111
|
+
<div style="text-align: <%= direction('right') %>; background-color: #fff;"><a href="#" class="close_instructure_ajax_error_box_link"><%= t('links.close', 'Close') %></a></div>
|
112
|
+
<iframe id="instructure_ajax_error_result" src="about:blank" style="border: 0;" title="<%= t('Error') %>"></iframe>
|
113
|
+
</div>
|
114
|
+
|
115
|
+
<div id="wrapper" class="ic-Layout-wrapper">
|
116
|
+
<% if crumbs.length > 1 %>
|
117
|
+
<% if @instui_topnav %>
|
118
|
+
<div class="instui-topnav-container">
|
119
|
+
<div id="react-instui-topnav"></div>
|
120
|
+
</div>
|
121
|
+
<% else %>
|
122
|
+
<div class="ic-app-nav-toggle-and-crumbs no-print">
|
123
|
+
<% if @show_left_side %>
|
124
|
+
<button type="button" id="courseMenuToggle" class="Button Button--link ic-app-course-nav-toggle" aria-live="polite" aria-label="<%= subnav_menu_text %>">
|
125
|
+
<i class="icon-hamburger" aria-hidden="true"></i>
|
126
|
+
</button>
|
127
|
+
<% end %>
|
128
|
+
|
129
|
+
<div class="ic-app-crumbs <%= 'ic-app-crumbs-enhanced-rubrics' if @enhanced_rubrics_enabled %>">
|
130
|
+
<% if @context&.is_a?(Course) && @context.elementary_subject_course? %>
|
131
|
+
<%= link_to course_path(id: @context.id), :class => "btn k5-back-to-subject", :id => "back_to_subject" do %>
|
132
|
+
<i class="icon-arrow-open-left"></i> <%= t('Back to Subject') %>
|
133
|
+
<% end %>
|
134
|
+
<% else %>
|
135
|
+
<%= render_crumbs %>
|
136
|
+
<% end %>
|
137
|
+
</div>
|
138
|
+
|
139
|
+
<% if @context&.is_a?(Course) && @context.elementary_subject_course? %>
|
140
|
+
<span class="k5-heading-course-name"><%= @context.nickname_for(@current_user) %></span>
|
141
|
+
<% end %>
|
142
|
+
|
143
|
+
<div class="right-of-crumbs">
|
144
|
+
<% if tutorials_enabled? %>
|
145
|
+
<div class="TutorialToggleHolder"></div>
|
146
|
+
<% end %>
|
147
|
+
<%if @domain_root_account&.feature_enabled?(:top_navigation_placement) %>
|
148
|
+
<div id="top-nav-tools-mount-point"></div>
|
149
|
+
<% end %>
|
150
|
+
<% if show_immersive_reader? %>
|
151
|
+
<div id="immersive_reader_mount_point"></div>
|
152
|
+
<% end %>
|
153
|
+
<% if show_student_view_button? %>
|
154
|
+
<%= link_to course_student_view_path(course_id: @context, redirect_to_referer: 1), :class => "btn btn-top-nav", :id => "easy_student_view", :method => :post, :role => "complementary", :"aria-label" => t("View as Student") do %>
|
155
|
+
<i class="icon-student-view"></i> <%= t('View as Student') %>
|
156
|
+
<% end %>
|
157
|
+
<% end %>
|
158
|
+
<% if (@context&.is_a?(Course) || @context&.is_a?(Assignment)) && @context_enrollment&.observer? %>
|
159
|
+
<div id='observer-picker-mountpoint' style="margin: 3px;"></div>
|
160
|
+
<% end %>
|
161
|
+
</div>
|
162
|
+
|
163
|
+
</div>
|
164
|
+
<% end %>
|
165
|
+
<% end %>
|
166
|
+
<div id="main" class="ic-Layout-columns">
|
167
|
+
<% if !@body_class_no_headers %>
|
168
|
+
<div class="ic-Layout-watermark"></div>
|
169
|
+
<% end %>
|
170
|
+
<% if @show_left_side %>
|
171
|
+
<% if @no_left_side_list_view
|
172
|
+
list_view_class = ''
|
173
|
+
else
|
174
|
+
list_view_class = 'list-view'
|
175
|
+
end
|
176
|
+
%>
|
177
|
+
<div id="left-side"
|
178
|
+
class="ic-app-course-menu ic-sticky-on <%= list_view_class %>"
|
179
|
+
style="display: <%= @collapse_course_menu ? "none" : "block" %>"
|
180
|
+
>
|
181
|
+
<div id="sticky-container" class="ic-sticky-frame">
|
182
|
+
<% if left_side %>
|
183
|
+
<%= left_side %>
|
184
|
+
<% else %>
|
185
|
+
<% if @context && @context.is_a?(Group) && can_do(@context, @current_user, :manage) && @context.group_category %>
|
186
|
+
<span id="group-switch-mount-point"></span>
|
187
|
+
<% end %>
|
188
|
+
<% if @context && @context.respond_to?(:enrollment_term) && !@context.enrollment_term.default_term? %>
|
189
|
+
<span id="section-tabs-header-subtitle" class="ellipsis"><%= @context.enrollment_term.name %></span>
|
190
|
+
<% end %>
|
191
|
+
<%= section_tabs %>
|
192
|
+
<% end %>
|
193
|
+
</div>
|
194
|
+
</div>
|
195
|
+
<% end %>
|
196
|
+
<div id="not_right_side" class="ic-app-main-content">
|
197
|
+
<div id="content-wrapper" class="ic-Layout-contentWrapper">
|
198
|
+
<% if should_show_migration_limitation_message %>
|
199
|
+
<% js_bundle :quiz_migration_alerts %>
|
200
|
+
<div class="ic-notification ic-notification--info quiz_migration_notification">
|
201
|
+
<div class="ic-notification__icon" role="presentation">
|
202
|
+
<i class="icon-info"></i>
|
203
|
+
<span class="screenreader-only">
|
204
|
+
<%= accessible_message_icon_text('information') %>
|
205
|
+
</span>
|
206
|
+
</div>
|
207
|
+
<div class="ic-notification__content">
|
208
|
+
<div class="ic-notification__message notification_message" style="margin-bottom:1rem;">
|
209
|
+
<%= t 'Your Classic Quizzes have been migrated to New Quizzes! ' %><br />
|
210
|
+
<%= t 'Please note:' %>
|
211
|
+
<ul>
|
212
|
+
<li><%= t 'Text No Question has moved to a Stimulus; please add a question so it can display within the quiz.' %></li>
|
213
|
+
</ul>
|
214
|
+
<%= t 'We apologize for the inconvenience and thank you for your patience as we continue to improve the migration experience!' %>
|
215
|
+
</div>
|
216
|
+
<div class="ic-notification__actions">
|
217
|
+
<a href="#"
|
218
|
+
rel="<%= api_v1_course_dismiss_migration_limitation_msg_url(@context.id) %>"
|
219
|
+
class="close_migration_notification_link Button Button--info"
|
220
|
+
role="button"
|
221
|
+
>
|
222
|
+
<%= t('Close') %>
|
223
|
+
</a>
|
224
|
+
</div>
|
225
|
+
</div>
|
226
|
+
</div>
|
227
|
+
<% end %>
|
228
|
+
<%= render :partial => 'shared/content_notices' if @has_content_notices && @show_left_side %>
|
229
|
+
<div id="content" class="ic-Layout-contentMain" role="main">
|
230
|
+
<%= yield %>
|
231
|
+
</div>
|
232
|
+
</div>
|
233
|
+
</div>
|
234
|
+
</div>
|
235
|
+
<% if @show_footer %>
|
236
|
+
<%= render :partial => 'shared/canvas_footer' %>
|
237
|
+
<% end %>
|
238
|
+
</div>
|
239
|
+
|
240
|
+
<% if @show_embedded_chat %>
|
241
|
+
<%= render :partial => 'shared/embedded_chat' %>
|
242
|
+
<% end %>
|
243
|
+
|
244
|
+
<% if @show_fixed_bottom %>
|
245
|
+
<%= render :partial => 'layouts/fixed_bottom' %>
|
246
|
+
<% end %>
|
247
|
+
|
248
|
+
<% if (wizard = (yield :wizard_box).presence) %>
|
249
|
+
<div id="wizard_box" tabindex="-1">
|
250
|
+
<div class="wizard_content">
|
251
|
+
<div class="links">
|
252
|
+
<a href="#" class="close_wizard_link"><i class="icon-x"></i><span class="screenreader-only"><%= t('links.close', 'Close') %></span></a>
|
253
|
+
</div>
|
254
|
+
<%= wizard %>
|
255
|
+
</div>
|
256
|
+
</div>
|
257
|
+
<% end %>
|
258
|
+
<% if (keyboard_navigation = (yield :keyboard_navigation).presence) %>
|
259
|
+
<div id="keyboard_navigation">
|
260
|
+
<%= keyboard_navigation %>
|
261
|
+
<div class='hidden-readable' tabindex='0'>
|
262
|
+
<%= t('keyboard_navigation.close', 'Press comma to close this dialog') %>
|
263
|
+
</div>
|
264
|
+
</div>
|
265
|
+
<% end %>
|
266
|
+
<div style="display:none;"><!-- Everything inside of this should always stay hidden -->
|
267
|
+
<% if @context && session && temp_type = session["role_#{@context.asset_string}"] %>
|
268
|
+
<span id="switched_role_type" class="<%= @context.asset_string %>" data-role="<%= temp_type %>"><%= Enrollment.readable_type(temp_type) %></span>
|
269
|
+
<% end %>
|
270
|
+
<% if @page_view %>
|
271
|
+
<div id="page_view_id"><%= @page_view.id %></div>
|
272
|
+
<% end %>
|
273
|
+
<% if equella_enabled? %>
|
274
|
+
<a id="equella_endpoint_url" href="<%= @equella_settings[:endpoint] %>"> </a>
|
275
|
+
<a id="equella_callback_url" href="<%= external_content_success_url('equella') %>"> </a>
|
276
|
+
<a id="equella_cancel_url" href="<%= external_content_cancel_url('equella') %>"> </a>
|
277
|
+
<a id="equella_action" href="<%= @equella_settings[:default_action] %>"> </a>
|
278
|
+
<% if @equella_settings[:teaser] %>
|
279
|
+
<div id="equella_teaser"><%= @equella_settings[:teaser] %></div>
|
280
|
+
<% end %>
|
281
|
+
<% end %>
|
282
|
+
</div>
|
283
|
+
<div id='aria_alerts' class='hide-text affix' role="alert" aria-live="assertive"></div>
|
284
|
+
<div id='StudentTray__Container'></div>
|
285
|
+
<% if tutorials_enabled? %>
|
286
|
+
<div class="NewUserTutorialTray__Container"></div>
|
287
|
+
<% end %>
|
288
|
+
<div id="react-router-portals"></div>
|
289
|
+
<%= render :partial => 'layouts/foot', :locals => { :include_common_bundle => true } %>
|
290
|
+
</div> <!-- #application -->
|
291
|
+
</body>
|
292
|
+
</html>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: explorak5_canvas_login
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Explorak5
|
@@ -20,10 +20,10 @@ extensions: []
|
|
20
20
|
extra_rdoc_files: []
|
21
21
|
files:
|
22
22
|
- lib/explorak5_canvas_login/app/views/dashboard/_dashboard_card.html.erb
|
23
|
+
- lib/explorak5_canvas_login/app/views/layout/_application_content.html.erb
|
23
24
|
- lib/explorak5_canvas_login/app/views/login/_login_content.html.erb
|
24
25
|
- lib/explorak5_canvas_login/app/views/login/_logout_confirm_content.html.erb
|
25
26
|
- lib/explorak5_canvas_login/app/views/navbar/_new_nav_header.html.erb
|
26
|
-
- lib/explorak5_canvas_login/ui/dashboard-card/react/DashboardCard.tsx
|
27
27
|
- lib/explorak5_canvas_login/version.rb
|
28
28
|
homepage: https://github.com/kevin523523/explorak5_canvas_login
|
29
29
|
licenses: []
|
@@ -1,372 +0,0 @@
|
|
1
|
-
// @ts-nocheck
|
2
|
-
/*
|
3
|
-
* Copyright (C) 2015 - present Instructure, Inc.
|
4
|
-
*
|
5
|
-
* This file is part of Canvas.
|
6
|
-
*
|
7
|
-
* Canvas is free software: you can redistribute it and/or modify it under
|
8
|
-
* the terms of the GNU Affero General Public License as published by the Free
|
9
|
-
* Software Foundation, version 3 of the License.
|
10
|
-
*
|
11
|
-
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
12
|
-
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
13
|
-
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
14
|
-
* details.
|
15
|
-
*
|
16
|
-
* You should have received a copy of the GNU Affero General Public License along
|
17
|
-
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
-
*/
|
19
|
-
|
20
|
-
import React, {MouseEventHandler, useCallback, useEffect, useRef, useState} from 'react'
|
21
|
-
import {useScope as useI18nScope} from '@canvas/i18n'
|
22
|
-
import axios from '@canvas/axios'
|
23
|
-
|
24
|
-
import DashboardCardAction from './DashboardCardAction'
|
25
|
-
import CourseActivitySummaryStore from './CourseActivitySummaryStore'
|
26
|
-
import DashboardCardMenu from './DashboardCardMenu'
|
27
|
-
import PublishButton from './PublishButton'
|
28
|
-
import {showConfirmUnfavorite} from './ConfirmUnfavoriteCourseModal'
|
29
|
-
import {showFlashError} from '@canvas/alerts/react/FlashAlert'
|
30
|
-
import instFSOptimizedImageUrl from '../util/instFSOptimizedImageUrl'
|
31
|
-
import {ConnectDragSource, ConnectDropTarget} from 'react-dnd'
|
32
|
-
|
33
|
-
const I18n = useI18nScope('dashcards')
|
34
|
-
|
35
|
-
export type DashboardCardHeaderHeroProps = {
|
36
|
-
image?: string
|
37
|
-
backgroundColor?: string
|
38
|
-
hideColorOverlays?: boolean
|
39
|
-
onClick?: MouseEventHandler<HTMLElement>
|
40
|
-
}
|
41
|
-
|
42
|
-
export const DashboardCardHeaderHero = ({
|
43
|
-
image,
|
44
|
-
backgroundColor,
|
45
|
-
hideColorOverlays,
|
46
|
-
onClick,
|
47
|
-
}: DashboardCardHeaderHeroProps) => {
|
48
|
-
if (image) {
|
49
|
-
return (
|
50
|
-
<div
|
51
|
-
className="ic-DashboardCard__header_image"
|
52
|
-
style={{backgroundImage: `url(${instFSOptimizedImageUrl(image, {x: 262, y: 146})})`}}
|
53
|
-
>
|
54
|
-
<div
|
55
|
-
className="ic-DashboardCard__header_hero"
|
56
|
-
style={{backgroundColor, opacity: hideColorOverlays ? 0 : 0.6}}
|
57
|
-
onClick={onClick}
|
58
|
-
aria-hidden="true"
|
59
|
-
/>
|
60
|
-
</div>
|
61
|
-
)
|
62
|
-
}
|
63
|
-
|
64
|
-
return (
|
65
|
-
<div
|
66
|
-
className="ic-DashboardCard__header_hero"
|
67
|
-
style={{backgroundColor}}
|
68
|
-
onClick={onClick}
|
69
|
-
aria-hidden="true"
|
70
|
-
/>
|
71
|
-
)
|
72
|
-
}
|
73
|
-
|
74
|
-
export type DashboardCardProps = {
|
75
|
-
id: string
|
76
|
-
backgroundColor?: string
|
77
|
-
shortName: string
|
78
|
-
originalName: string
|
79
|
-
courseCode: string
|
80
|
-
assetString: string
|
81
|
-
term?: string
|
82
|
-
href: string
|
83
|
-
links: any[] // TODO: improve type
|
84
|
-
image?: string
|
85
|
-
handleColorChange?: (color: string) => void
|
86
|
-
hideColorOverlays?: boolean
|
87
|
-
isDragging?: boolean
|
88
|
-
isFavorited?: boolean
|
89
|
-
connectDragSource?: ConnectDragSource
|
90
|
-
connectDropTarget?: ConnectDropTarget
|
91
|
-
moveCard?: (assetString: string, atIndex: number, callback: () => void) => void
|
92
|
-
onConfirmUnfavorite: (id: string) => void
|
93
|
-
totalCards?: number
|
94
|
-
position?: number | (() => number)
|
95
|
-
enrollmentType?: string
|
96
|
-
observee?: string
|
97
|
-
published?: boolean
|
98
|
-
canChangeCoursePublishState?: boolean
|
99
|
-
defaultView?: string
|
100
|
-
pagesUrl?: string
|
101
|
-
frontPageTitle?: string
|
102
|
-
onPublishedCourse?: (id: string) => void
|
103
|
-
}
|
104
|
-
|
105
|
-
export const DashboardCard = ({
|
106
|
-
id,
|
107
|
-
backgroundColor = '#394B58',
|
108
|
-
shortName,
|
109
|
-
originalName,
|
110
|
-
courseCode,
|
111
|
-
assetString,
|
112
|
-
term,
|
113
|
-
href,
|
114
|
-
links = [],
|
115
|
-
image,
|
116
|
-
handleColorChange = () => {},
|
117
|
-
hideColorOverlays,
|
118
|
-
isDragging,
|
119
|
-
isFavorited,
|
120
|
-
connectDragSource = c => c,
|
121
|
-
connectDropTarget = c => c,
|
122
|
-
moveCard = () => {},
|
123
|
-
onConfirmUnfavorite,
|
124
|
-
totalCards = 0,
|
125
|
-
position = 0,
|
126
|
-
enrollmentType,
|
127
|
-
observee,
|
128
|
-
published,
|
129
|
-
canChangeCoursePublishState,
|
130
|
-
defaultView,
|
131
|
-
pagesUrl,
|
132
|
-
frontPageTitle,
|
133
|
-
onPublishedCourse = () => {},
|
134
|
-
}: DashboardCardProps) => {
|
135
|
-
const handleNicknameChange = nickname => setNicknameInfo(getNicknameInfo(nickname))
|
136
|
-
|
137
|
-
const getNicknameInfo = (nickname: string) => ({
|
138
|
-
nickname,
|
139
|
-
originalName,
|
140
|
-
courseId: id,
|
141
|
-
onNicknameChange: handleNicknameChange,
|
142
|
-
})
|
143
|
-
|
144
|
-
const [nicknameInfo, setNicknameInfo] = useState(getNicknameInfo(shortName))
|
145
|
-
const [course, setCourse] = useState(CourseActivitySummaryStore.getStateForCourse(id))
|
146
|
-
const settingsToggle = useRef<HTMLButtonElement | null>()
|
147
|
-
|
148
|
-
const handleStoreChange = useCallback(
|
149
|
-
() => setCourse(CourseActivitySummaryStore.getStateForCourse(id)),
|
150
|
-
[id]
|
151
|
-
)
|
152
|
-
|
153
|
-
useEffect(() => {
|
154
|
-
CourseActivitySummaryStore.addChangeListener(handleStoreChange)
|
155
|
-
return () => CourseActivitySummaryStore.removeChangeListener(handleStoreChange)
|
156
|
-
}, [handleStoreChange])
|
157
|
-
|
158
|
-
// ===============
|
159
|
-
// ACTIONS
|
160
|
-
// ===============
|
161
|
-
|
162
|
-
const getCardPosition = () => (typeof position === 'function' ? position() : position)
|
163
|
-
|
164
|
-
const headerClick: MouseEventHandler = e => {
|
165
|
-
e.preventDefault()
|
166
|
-
window.location.assign(href)
|
167
|
-
}
|
168
|
-
|
169
|
-
const handleMove = (asset: string, atIndex: number) => {
|
170
|
-
if (moveCard) {
|
171
|
-
moveCard(asset, atIndex, () => settingsToggle.current?.focus())
|
172
|
-
}
|
173
|
-
}
|
174
|
-
|
175
|
-
const handleUnfavorite = () => {
|
176
|
-
const modalProps = {
|
177
|
-
courseId: id,
|
178
|
-
courseName: originalName,
|
179
|
-
onConfirm: removeCourseFromFavorites,
|
180
|
-
}
|
181
|
-
showConfirmUnfavorite(modalProps)
|
182
|
-
}
|
183
|
-
|
184
|
-
// ===============
|
185
|
-
// HELPERS
|
186
|
-
// ===============
|
187
|
-
|
188
|
-
const unreadCount = (icon: string, stream?: any[]) => {
|
189
|
-
const activityType = {
|
190
|
-
'icon-announcement': 'Announcement',
|
191
|
-
'icon-assignment': 'Message',
|
192
|
-
'icon-discussion': 'DiscussionTopic',
|
193
|
-
}[icon]
|
194
|
-
|
195
|
-
const itemStream = stream || []
|
196
|
-
const streamItem = itemStream.find(
|
197
|
-
item =>
|
198
|
-
// only return 'Message' type if category is 'Due Date' (for assignments)
|
199
|
-
item.type === activityType &&
|
200
|
-
(activityType !== 'Message' || item.notification_category === I18n.t('Due Date'))
|
201
|
-
)
|
202
|
-
|
203
|
-
// TODO: unread count is always 0 for assignments (see CNVS-21227)
|
204
|
-
return streamItem ? streamItem.unread_count : 0
|
205
|
-
}
|
206
|
-
|
207
|
-
const calculateMenuOptions = () => {
|
208
|
-
const cardPosition = getCardPosition()
|
209
|
-
const isFirstCard = cardPosition === 0
|
210
|
-
const isLastCard = cardPosition === totalCards - 1
|
211
|
-
return {
|
212
|
-
canMoveLeft: !isFirstCard,
|
213
|
-
canMoveRight: !isLastCard,
|
214
|
-
canMoveToBeginning: !isFirstCard,
|
215
|
-
canMoveToEnd: !isLastCard,
|
216
|
-
}
|
217
|
-
}
|
218
|
-
|
219
|
-
const removeCourseFromFavorites = () => {
|
220
|
-
const url = `/api/v1/users/self/favorites/courses/${id}`
|
221
|
-
axios
|
222
|
-
.delete(url)
|
223
|
-
.then(response => {
|
224
|
-
if (response.status === 200) {
|
225
|
-
onConfirmUnfavorite(id)
|
226
|
-
}
|
227
|
-
})
|
228
|
-
.catch(() =>
|
229
|
-
showFlashError(I18n.t('We were unable to remove this course from your favorites.'))
|
230
|
-
)
|
231
|
-
}
|
232
|
-
|
233
|
-
const updatePublishedCourse = () => {
|
234
|
-
if (onPublishedCourse) onPublishedCourse(id)
|
235
|
-
}
|
236
|
-
|
237
|
-
// ===============
|
238
|
-
// RENDERING
|
239
|
-
// ===============
|
240
|
-
|
241
|
-
const linksForCard = () =>
|
242
|
-
links.map(link => {
|
243
|
-
if (link.hidden) return null
|
244
|
-
|
245
|
-
const screenReaderLabel = `${link.label} - ${nicknameInfo.nickname}`
|
246
|
-
return (
|
247
|
-
<DashboardCardAction
|
248
|
-
unreadCount={unreadCount(link.icon, course?.stream)}
|
249
|
-
iconClass={link.icon}
|
250
|
-
linkClass={link.css_class}
|
251
|
-
path={link.path}
|
252
|
-
screenReaderLabel={screenReaderLabel}
|
253
|
-
key={link.path}
|
254
|
-
/>
|
255
|
-
)
|
256
|
-
})
|
257
|
-
|
258
|
-
const renderHeaderButton = () => {
|
259
|
-
const reorderingProps = {
|
260
|
-
handleMove,
|
261
|
-
currentPosition: getCardPosition(),
|
262
|
-
lastPosition: totalCards - 1,
|
263
|
-
menuOptions: calculateMenuOptions(),
|
264
|
-
}
|
265
|
-
|
266
|
-
return (
|
267
|
-
<div>
|
268
|
-
<div
|
269
|
-
className="ic-DashboardCard__header-button-bg"
|
270
|
-
style={{backgroundColor, opacity: hideColorOverlays ? 1 : 0}}
|
271
|
-
/>
|
272
|
-
<DashboardCardMenu
|
273
|
-
afterUpdateColor={(c: string) => handleColorChange(`#${c}`)}
|
274
|
-
currentColor={backgroundColor}
|
275
|
-
nicknameInfo={nicknameInfo}
|
276
|
-
assetString={assetString}
|
277
|
-
onUnfavorite={handleUnfavorite}
|
278
|
-
isFavorited={isFavorited}
|
279
|
-
{...reorderingProps}
|
280
|
-
trigger={
|
281
|
-
<button
|
282
|
-
type="button"
|
283
|
-
className="Button Button--icon-action-rev ic-DashboardCard__header-button"
|
284
|
-
ref={c => {
|
285
|
-
settingsToggle.current = c
|
286
|
-
}}
|
287
|
-
>
|
288
|
-
<i className="icon-more" aria-hidden="true" />
|
289
|
-
<span className="screenreader-only">
|
290
|
-
{I18n.t('Choose a color or course nickname or move course card for %{course}', {
|
291
|
-
course: nicknameInfo.nickname,
|
292
|
-
})}
|
293
|
-
</span>
|
294
|
-
</button>
|
295
|
-
}
|
296
|
-
/>
|
297
|
-
</div>
|
298
|
-
)
|
299
|
-
}
|
300
|
-
|
301
|
-
const dashboardCard = (
|
302
|
-
<div
|
303
|
-
className="ic-DashboardCard"
|
304
|
-
style={{opacity: isDragging ? 0 : 1}}
|
305
|
-
aria-label={originalName}
|
306
|
-
>
|
307
|
-
<div className="ic-DashboardCard__header" style={{backgroundColor}}>
|
308
|
-
<span className="screenreader-only">
|
309
|
-
{image
|
310
|
-
? I18n.t('Course image for %{course}', {course: nicknameInfo.nickname})
|
311
|
-
: I18n.t('Course card color region for %{course}', {
|
312
|
-
course: nicknameInfo.nickname,
|
313
|
-
})}
|
314
|
-
</span>
|
315
|
-
<DashboardCardHeaderHero
|
316
|
-
image={image}
|
317
|
-
backgroundColor={backgroundColor}
|
318
|
-
hideColorOverlays={hideColorOverlays}
|
319
|
-
onClick={headerClick}
|
320
|
-
/>
|
321
|
-
<a href={href} className="ic-DashboardCard__link">
|
322
|
-
<div className="ic-DashboardCard__header_content" style={{backgroundColor}}>
|
323
|
-
<h5
|
324
|
-
className="ic-DashboardCard__header-title ellipsis"
|
325
|
-
title={originalName}
|
326
|
-
style={{color: backgroundColor}}
|
327
|
-
>
|
328
|
-
<span style={{color: backgroundColor}}>{nicknameInfo.nickname}</span>
|
329
|
-
</h5>
|
330
|
-
<div
|
331
|
-
className="ic-DashboardCard__header-term ellipsis"
|
332
|
-
title={term}
|
333
|
-
style={{color: backgroundColor}}
|
334
|
-
>
|
335
|
-
{term || null}
|
336
|
-
</div>
|
337
|
-
{enrollmentType === 'ObserverEnrollment' && observee && (
|
338
|
-
<div
|
339
|
-
className="ic-DashboardCard__header-term ellipsis"
|
340
|
-
title={observee}
|
341
|
-
style={{color: backgroundColor}}
|
342
|
-
>
|
343
|
-
{I18n.t('Observing: %{observee}', {observee})}
|
344
|
-
</div>
|
345
|
-
)}
|
346
|
-
</div>
|
347
|
-
</a>
|
348
|
-
{!published && canChangeCoursePublishState && (
|
349
|
-
<PublishButton
|
350
|
-
courseNickname={nicknameInfo.nickname}
|
351
|
-
defaultView={defaultView}
|
352
|
-
pagesUrl={pagesUrl}
|
353
|
-
frontPageTitle={frontPageTitle}
|
354
|
-
courseId={id}
|
355
|
-
/>
|
356
|
-
)}
|
357
|
-
{renderHeaderButton()}
|
358
|
-
</div>
|
359
|
-
<nav
|
360
|
-
className="ic-DashboardCard__action-container"
|
361
|
-
aria-label={I18n.t('Actions for %{course}', {course: nicknameInfo.nickname})}
|
362
|
-
style={{color: backgroundColor}}
|
363
|
-
>
|
364
|
-
{linksForCard()}
|
365
|
-
</nav>
|
366
|
-
</div>
|
367
|
-
)
|
368
|
-
|
369
|
-
return connectDragSource(connectDropTarget(dashboardCard))
|
370
|
-
}
|
371
|
-
|
372
|
-
export default DashboardCard
|