Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
.idea

*.tgz
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 13 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
"description": "Common UI components that are commonly used for LTI tools in the Canvas LMS",
"module": "dist/ui-lti.js",
"main": "dist/ui-lti.cjs",
"types": "dist/index.d.ts",
"exports": {
"import": "./dist/ui-lti.js",
"require": "./dist/ui-lti.cjs"
".": {
"types": "./dist/index.d.ts",
"import": "./dist/ui-lti.js",
"require": "./dist/ui-lti.cjs"
}
},
"repository": {
"url": "https://github.com/oxctl/ui-lti"
Expand All @@ -20,13 +24,14 @@
"storybook": "sb dev -- -p 6006",
"storybook-https": "sb dev -- -p 6006 --https --ssl-cert localhost.pem --ssl-key localhost-key.pem",
"build-storybook": "sb build",
"build": "vite build",
"build": "vite build && tsc -p tsconfig.build.json",
"prepack": "npm run build",
"start": "vite",
"test": "vitest"
},
"files": [
"src/*",
"dist/*"
"dist/**",
"src/**"
],
"devDependencies": {
"@instructure/canvas-theme": "^10.18.0",
Expand All @@ -51,13 +56,16 @@
"@testing-library/jest-dom": "^6.6.2",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.4.1",
"jsdom": "^25.0.1",
"msw": "^2.6.6",
"msw-storybook-addon": "^2.0.4",
"prop-types": "^15.7.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.5.4",
"vite": "6.4.1",
"vitest": "^3.2.4"
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/LtiPageSettings/LtiPageSettings.stories.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import {LtiPageSettings} from './LtiPageSettings.jsx'
import {LtiPageSettings} from './LtiPageSettings'
import { ColorIndicator } from '@instructure/ui-color-picker'
import { View } from '@instructure/ui-view'
import { Avatar } from '@instructure/ui-avatar'
Expand Down
2 changes: 1 addition & 1 deletion src/components/LtiPageSettings/LtiPageSettings.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {useContext} from 'react'
import '@testing-library/jest-dom';
import {render, screen, waitFor} from '@testing-library/react'
import {describe, expect, it} from 'vitest'
import {LtiPageSettings, PageSettingsContext} from "./LtiPageSettings.jsx";
import {LtiPageSettings, PageSettingsContext} from "./LtiPageSettings";

describe('LtiPageSettings Test Suite', () => {
it('renders children correctly and sets context', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
import { createContext, useEffect, useState } from 'react'
import type { ReactNode } from 'react'
import { InstUISettingsProvider } from '@instructure/emotion'
import { canvas, canvasHighContrast } from '@instructure/ui-themes'

/**
* This context provides the page settings that nested components can use.
* @type {React.Context<{}>}
*/
const PageSettingsContext = createContext({})
type PageSettings = Record<string, unknown>

const PageSettingsContext = createContext<PageSettings>({})

/**
* This component retrieves the page settings from Canvas and if a theme is provided loads it and applies it to the children.
* @param children the components to render which will be themed
* @param debug if true, debug messages will be logged to the console
* @param themeRetries the number of times to retry fetching the theme if it fails
*/
function LtiPageSettings({ children, debug = false, themeRetries = 1 }) {
type LtiPageSettingsProps = {
children: ReactNode
debug?: boolean
themeRetries?: number
}

function LtiPageSettings({ children, debug = false, themeRetries = 1 }: LtiPageSettingsProps) {
// A copy of the pages settings sent from Canvas.
const [pageSettings, setPageSettings] = useState({})
const [pageSettings, setPageSettings] = useState<PageSettings>({})
// The theme for Instructure UI components.
const [theme, setTheme] = useState({})
const [theme, setTheme] = useState<Record<string, unknown>>({})

/**
* This is a debug function that will log messages to the console if debug is enabled.
* @param message Message to log
*/
const logDebug = (message) => {
const logDebug = (message: string) => {
debug && console.debug(message)
}

const fetchTheme = async (themeUrl) => {
const fetchTheme = async (themeUrl?: string) => {
if (themeUrl) {
for (let attempt = 0; attempt <= themeRetries; attempt++) {
try {
Expand All @@ -50,19 +59,19 @@ function LtiPageSettings({ children, debug = false, themeRetries = 1 }) {
useEffect(() => {
let receivedPageSettings = false
const targetWindow = window.parent || window.opener
const messageHandler = (event) => {
if (event.data.subject === 'lti.getPageSettings.response') {
const messageHandler = (event: MessageEvent) => {
if (event.data?.subject === 'lti.getPageSettings.response') {
logDebug('Received page settings response from: ' + event.origin + ' with data: ' + JSON.stringify(event.data, null, 2))
const pageSettings = event.data.pageSettings
const pageSettings = event.data.pageSettings as PageSettings | undefined
if (pageSettings) {
setPageSettings(pageSettings)
receivedPageSettings = true
const highContrast = pageSettings.use_high_contrast
const highContrast = pageSettings.use_high_contrast as boolean | undefined
if (highContrast) {
logDebug('High contrast mode enabled, not loading custom theme')
setTheme(canvasHighContrast)
} else {
const themeUrl = pageSettings.active_brand_config_json_url
const themeUrl = pageSettings.active_brand_config_json_url as string | undefined
logDebug('Loading theme from URL: ' + themeUrl)
fetchTheme(themeUrl)
.then(variables => {
Expand Down Expand Up @@ -106,4 +115,4 @@ function LtiPageSettings({ children, debug = false, themeRetries = 1 }) {
)
}

export { LtiPageSettings, PageSettingsContext }
export { LtiPageSettings, PageSettingsContext }
2 changes: 1 addition & 1 deletion src/components/applyTheme/LtiApplyTheme.stories.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import LtiApplyTheme from './LtiApplyTheme.jsx'
import LtiApplyTheme from './LtiApplyTheme'
import { ColorIndicator } from '@instructure/ui-color-picker'
import { View } from '@instructure/ui-view'
import { Avatar } from '@instructure/ui-avatar'
Expand Down
2 changes: 1 addition & 1 deletion src/components/applyTheme/LtiApplyTheme.test.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import '@testing-library/jest-dom';
import LtiApplyTheme from "./LtiApplyTheme.jsx";
import LtiApplyTheme from "./LtiApplyTheme";
import {act, render, screen} from '@testing-library/react'
import {describe, expect, it} from 'vitest'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,22 @@ import PropTypes from 'prop-types'
import { InstUISettingsProvider } from '@instructure/emotion'
import { canvasHighContrast, canvas } from '@instructure/ui-themes'

type LtiApplyThemeProps = {
url?: string | null
highContrast?: boolean
children: React.ReactNode
maxRetries?: number
}

type LtiApplyThemeState = {
theme: Record<string, unknown>
}

/**
* This attempts to load the theme from the supplied URL and then applies the theme to all the children.
* If this isn't working, it's very possible you have multiple copies of instructure ui
*/
export class LtiApplyTheme extends React.Component {
export class LtiApplyTheme extends React.Component<LtiApplyThemeProps, LtiApplyThemeState> {

static propTypes = {
/**
Expand All @@ -26,7 +37,7 @@ export class LtiApplyTheme extends React.Component {
}

loading = false
state = {
state: LtiApplyThemeState = {
theme: {}
}

Expand All @@ -37,9 +48,10 @@ export class LtiApplyTheme extends React.Component {
/**
* This fetches the custom theme variables for the instance.
*/
fetchVariables = async () => {
fetchVariables = async (): Promise<Record<string, unknown>> => {
if (this.props.url) {
for (let attempt = 0; attempt <= this.props.maxRetries; attempt++) {
const maxRetries = this.props.maxRetries ?? 1
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const variables = await fetch(this.props.url)
.then(response => {
Expand Down Expand Up @@ -70,7 +82,7 @@ export class LtiApplyTheme extends React.Component {
}
}

componentDidUpdate(prevProps, prevState, snapshot) {
componentDidUpdate(prevProps: LtiApplyThemeProps) {
if (this.props.url !== prevProps.url
|| this.props.highContrast !== prevProps.highContrast
) {
Expand All @@ -87,4 +99,4 @@ export class LtiApplyTheme extends React.Component {
}
}

export default LtiApplyTheme
export default LtiApplyTheme
2 changes: 1 addition & 1 deletion src/components/errorBillboard/ErrorBillboard.stories.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import ErrorBillboard from './ErrorBillboard.jsx';
import ErrorBillboard from './ErrorBillboard';

const Template = (args) => <ErrorBillboard {...args}/>

Expand Down
2 changes: 1 addition & 1 deletion src/components/errorBillboard/ErrorBillboard.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import '@testing-library/jest-dom';
import {render, screen} from '@testing-library/react'
import {describe, expect, it} from 'vitest'
import ErrorBillboard from "./ErrorBillboard.jsx";
import ErrorBillboard from "./ErrorBillboard";

const mockHeader = "Test Error Heading"
let mockMessage = "Test Message"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ import React from 'react'
import PropTypes from 'prop-types'
import {Billboard} from '@instructure/ui-billboard'
import {IconWarningLine} from '@instructure/ui-icons'
import {HeroIconSize} from "@instructure/ui-billboard/types/Billboard/props";

type ErrorBillboardProps = {
heading?: string
message?: string | null
children: React.ReactNode
}

/**
* This either renders the child components or the error if it's present.
* This is designed to handle toplevel errors when something has gone seriously wrong and we don't want to display
* the rest of the application.
*/
export class ErrorBillBoard extends React.Component {
export class ErrorBillBoard extends React.Component<ErrorBillboardProps> {

static propTypes = {
// The heading of the error.
Expand All @@ -31,9 +38,9 @@ export class ErrorBillBoard extends React.Component {
heading={heading}
message={message}
size="large"
hero={(size) => <IconWarningLine size={size}/>}
hero={(size: HeroIconSize) => <IconWarningLine size={size}/>}
/>) : children
}
}

export default ErrorBillBoard
export default ErrorBillBoard
2 changes: 1 addition & 1 deletion src/components/heightLimit/LtiHeightLimit.stories.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import LtiHeightLimit from './LtiHeightLimit.jsx';
import LtiHeightLimit from './LtiHeightLimit';

const Template = () => <LtiHeightLimit>
<div> Test Height Limit </div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/heightLimit/LtiHeightLimit.test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import '@testing-library/jest-dom';
import LtiHeightLimit from "./LtiHeightLimit.jsx";
import LtiHeightLimit from "./LtiHeightLimit";
import {render, screen} from '@testing-library/react'
import {describe, expect, it, vi} from 'vitest'

Expand Down
Loading