caccl-api
Version:
A class that defines a set of smart Canvas endpoints that actually behave how you'd expect them to.
233 lines (214 loc) • 7.12 kB
text/typescript
/**
* Functions for interacting with course navigation menu items
* @namespace api.course.navMenuItem
*/
// Import caccl libs
import CACCLError from 'caccl-error';
// Import shared classes
import EndpointCategory from '../../shared/EndpointCategory';
// Import shared types
import APIConfig from '../../shared/types/APIConfig';
import CanvasTab from '../../types/CanvasTab';
import ErrorCode from '../../shared/types/ErrorCode';
// Import shared constants
import API_PREFIX from '../../shared/constants/API_PREFIX';
// Endpoint category
class ECatNavMenuItem extends EndpointCategory {
/*------------------------------------------------------------------------*/
/* Nav Menu Endpoints */
/*------------------------------------------------------------------------*/
/**
* Lists the nav menu items in the course
* @author Gabe Abrams
* @method list
* @memberof api.course.navMenuItem
* @instance
* @async
* @param {object} [opts] object containing all arguments
* @param {number} [opts.courseId] Canvas course Id
* @param {APIConfig} [config] custom configuration for this specific endpoint
* call (overwrites defaults that were included when api was initialized)
* @returns {Promise<CanvasTab[]>} list of Canvas Tabs {@link https://canvas.instructure.com/doc/api/tabs.html#method.tabs.index}
*/
public async list(
opts: {
courseId?: number,
} = {},
config?: APIConfig,
): Promise<CanvasTab[]> {
return this.visitEndpoint({
config,
action: 'get the list of nav menu items in a course',
path: `${API_PREFIX}/courses/${opts.courseId ?? this.defaultCourseId}/tabs`,
method: 'GET',
});
}
/**
* Update a nav menu item
* @author Gabe Abrams
* @method update
* @memberof api.course.navMenuItem
* @instance
* @async
* @param {object} [opts] object containing all arguments
* @param {number} [opts.courseId=default course id] Canvas course Id
* @param {string} [opts.url] a url string identifying the item
* to move to the top of the menu. The url must either be a full url or
* a path.
* At least one of url, label, or id must
* be included. Case insensitive
* @param {string} [opts.label] a text label identifying the item
* to move to the top of the menu. At least one of url, label, or id must
* be included. Case insensitive.
* @param {string} [opts.id] the id of the item to move to the top of
* the menu. At least one of url, label, or id must be included. Case
* sensitive.
* @param {boolean} [opts.moveToTop] if true, moves the given nav menu
* item as high up in the nav menu as allowed by Canvas. At best, the position
* will be set to 2 because position 1 is reserved for the "Home" item.
* @param {number} [opts.position] the new position of the item (starts
* at 1)
* @param {boolean} [opts.hidden] if true, menu item is hidden.
* if false, menu item is made visible. if excluded, visibility is unchanged.
* @param {APIConfig} [config] custom configuration for this specific endpoint
* call (overwrites defaults that were included when api was initialized)
* @returns {Promise<CanvasTab>} Canvas Tab {@link https://canvas.instructure.com/doc/api/tabs.html#method.tabs.index}
*/
public async update(
opts: {
courseId?: number,
url?: string,
label?: string,
id?: string,
moveToTop?: boolean,
position?: number,
hidden?: boolean,
} = {},
config?: APIConfig,
): Promise<CanvasTab> {
// Create the update object
const params: { [k: string]: any } = {};
// > Add position
if (opts.moveToTop) {
params.position = 2;
} else if (opts.position) {
params.position = opts.position;
}
// > Add hidden
if (opts.hidden !== undefined) {
params.hidden = !!opts.hidden;
}
// Just return if no updates to be made
if (Object.keys(params).length === 0) {
return;
}
// Get the list of nav menu items
const tabs = await this.api.course.navMenuItem.list(
{
courseId: (opts.courseId ?? this.defaultCourseId),
},
config,
);
// Find the item we are looking for
let tab: CanvasTab;
for (let i = 0; i < tabs.length; i++) {
// Match based on id
if (
opts.id
&& (
String(tabs[i].id).trim()
=== String(opts.id).trim()
)
) {
tab = tabs[i];
break;
}
// Match based on url
if (
opts.url
&& (
tabs[i].html_url.toLowerCase()
=== opts.url.toLowerCase()
)
) {
tab = tabs[i];
break;
}
// Match based on label
if (
opts.label
&& (
(
String(tabs[i].label)
.trim()
.toLowerCase()
)
=== (
String(opts.label)
.trim()
.toLowerCase()
)
)
) {
tab = tabs[i];
break;
}
}
// Error if no menu item found
if (!tab) {
throw new CACCLError({
message: 'We could not find the menu item of interest.',
code: ErrorCode.NavItemNotFound,
});
}
// Keep same value for hidden field if unchanged
if (params.hidden === undefined) {
params.hidden = !!tab.hidden;
}
/**
* Attempt to modify tab
* @author Gabe Abrams
*/
const makeAttempt = async (): Promise<CanvasTab> => {
// Update the item
try {
const results = await this.visitEndpoint({
config,
action: 'update a nav menu item in a course',
params,
path: `${API_PREFIX}/courses/${opts.courseId ?? this.defaultCourseId}/tabs/${tab.id}`,
method: 'PUT',
});
return results;
} catch (err) {
// Check for invalid location errors we can fix
if (
err.code === ErrorCode.InvalidTabLocation
&& opts.moveToTop
) {
// Keep trying larger numbered positions
if (params.position >= tabs.length) {
// Already at the max position
throw new CACCLError({
message: 'In an attempt to move a nav item to the top of the menu, we tried every position and Canvas denied the change.',
code: ErrorCode.TriedAllTabLocations,
});
}
// We can still try the next position
// > Increment the position
params.position += 1;
// > Make another attempt
return makeAttempt();
}
// Other error. Rethrow it
throw err;
}
};
// Start attempts
return makeAttempt();
}
}
/*------------------------------------------------------------------------*/
/* Export */
/*------------------------------------------------------------------------*/
export default ECatNavMenuItem;