@pie-players/pie-assessment-toolkit 0.2.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.
- package/README.md +520 -0
- package/dist/attempt/TestSession.d.ts +90 -0
- package/dist/attempt/TestSession.d.ts.map +1 -0
- package/dist/attempt/TestSession.js +174 -0
- package/dist/attempt/TestSession.js.map +1 -0
- package/dist/core/TypedEventBus.d.ts +48 -0
- package/dist/core/TypedEventBus.d.ts.map +1 -0
- package/dist/core/TypedEventBus.js +63 -0
- package/dist/core/TypedEventBus.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/item-loader.d.ts +45 -0
- package/dist/item-loader.d.ts.map +1 -0
- package/dist/item-loader.js +62 -0
- package/dist/item-loader.js.map +1 -0
- package/dist/player/AssessmentPlayer.d.ts +388 -0
- package/dist/player/AssessmentPlayer.d.ts.map +1 -0
- package/dist/player/AssessmentPlayer.js +965 -0
- package/dist/player/AssessmentPlayer.js.map +1 -0
- package/dist/player/index.d.ts +17 -0
- package/dist/player/index.d.ts.map +1 -0
- package/dist/player/index.js +15 -0
- package/dist/player/index.js.map +1 -0
- package/dist/player/navigation-types.d.ts +63 -0
- package/dist/player/navigation-types.d.ts.map +1 -0
- package/dist/player/navigation-types.js +7 -0
- package/dist/player/navigation-types.js.map +1 -0
- package/dist/player/qti-navigation.d.ts +29 -0
- package/dist/player/qti-navigation.d.ts.map +1 -0
- package/dist/player/qti-navigation.js +160 -0
- package/dist/player/qti-navigation.js.map +1 -0
- package/dist/reference-layout/index.d.ts +19 -0
- package/dist/reference-layout/index.d.ts.map +1 -0
- package/dist/reference-layout/index.js +20 -0
- package/dist/reference-layout/index.js.map +1 -0
- package/dist/services/AccessibilityCatalogResolver.d.ts +141 -0
- package/dist/services/AccessibilityCatalogResolver.d.ts.map +1 -0
- package/dist/services/AccessibilityCatalogResolver.js +249 -0
- package/dist/services/AccessibilityCatalogResolver.js.map +1 -0
- package/dist/services/AssessmentAuthoringService.d.ts +60 -0
- package/dist/services/AssessmentAuthoringService.d.ts.map +1 -0
- package/dist/services/AssessmentAuthoringService.js +183 -0
- package/dist/services/AssessmentAuthoringService.js.map +1 -0
- package/dist/services/ContextVariableStore.d.ts +114 -0
- package/dist/services/ContextVariableStore.d.ts.map +1 -0
- package/dist/services/ContextVariableStore.js +207 -0
- package/dist/services/ContextVariableStore.js.map +1 -0
- package/dist/services/ElementToolStateStore.d.ts +125 -0
- package/dist/services/ElementToolStateStore.d.ts.map +1 -0
- package/dist/services/ElementToolStateStore.js +200 -0
- package/dist/services/ElementToolStateStore.js.map +1 -0
- package/dist/services/HighlightCoordinator.d.ts +179 -0
- package/dist/services/HighlightCoordinator.d.ts.map +1 -0
- package/dist/services/HighlightCoordinator.js +446 -0
- package/dist/services/HighlightCoordinator.js.map +1 -0
- package/dist/services/I18nService.d.ts +121 -0
- package/dist/services/I18nService.d.ts.map +1 -0
- package/dist/services/I18nService.js +276 -0
- package/dist/services/I18nService.js.map +1 -0
- package/dist/services/PNPMapper.d.ts +71 -0
- package/dist/services/PNPMapper.d.ts.map +1 -0
- package/dist/services/PNPMapper.js +98 -0
- package/dist/services/PNPMapper.js.map +1 -0
- package/dist/services/PNPToolResolver.d.ts +127 -0
- package/dist/services/PNPToolResolver.d.ts.map +1 -0
- package/dist/services/PNPToolResolver.js +222 -0
- package/dist/services/PNPToolResolver.js.map +1 -0
- package/dist/services/SSMLExtractor.d.ts +61 -0
- package/dist/services/SSMLExtractor.d.ts.map +1 -0
- package/dist/services/SSMLExtractor.js +181 -0
- package/dist/services/SSMLExtractor.js.map +1 -0
- package/dist/services/TTSService.d.ts +162 -0
- package/dist/services/TTSService.d.ts.map +1 -0
- package/dist/services/TTSService.js +465 -0
- package/dist/services/TTSService.js.map +1 -0
- package/dist/services/ThemeProvider.d.ts +95 -0
- package/dist/services/ThemeProvider.d.ts.map +1 -0
- package/dist/services/ThemeProvider.js +299 -0
- package/dist/services/ThemeProvider.js.map +1 -0
- package/dist/services/ToolConfigResolver.d.ts +162 -0
- package/dist/services/ToolConfigResolver.d.ts.map +1 -0
- package/dist/services/ToolConfigResolver.js +230 -0
- package/dist/services/ToolConfigResolver.js.map +1 -0
- package/dist/services/ToolCoordinator.d.ts +137 -0
- package/dist/services/ToolCoordinator.d.ts.map +1 -0
- package/dist/services/ToolCoordinator.js +318 -0
- package/dist/services/ToolCoordinator.js.map +1 -0
- package/dist/services/ToolkitCoordinator.d.ts +188 -0
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -0
- package/dist/services/ToolkitCoordinator.js +252 -0
- package/dist/services/ToolkitCoordinator.js.map +1 -0
- package/dist/services/interfaces.d.ts +391 -0
- package/dist/services/interfaces.d.ts.map +1 -0
- package/dist/services/interfaces.js +12 -0
- package/dist/services/interfaces.js.map +1 -0
- package/dist/services/tool-providers/DesmosToolProvider.d.ts +103 -0
- package/dist/services/tool-providers/DesmosToolProvider.d.ts.map +1 -0
- package/dist/services/tool-providers/DesmosToolProvider.js +127 -0
- package/dist/services/tool-providers/DesmosToolProvider.js.map +1 -0
- package/dist/services/tool-providers/IToolProvider.d.ts +137 -0
- package/dist/services/tool-providers/IToolProvider.d.ts.map +1 -0
- package/dist/services/tool-providers/IToolProvider.js +13 -0
- package/dist/services/tool-providers/IToolProvider.js.map +1 -0
- package/dist/services/tool-providers/TIToolProvider.d.ts +107 -0
- package/dist/services/tool-providers/TIToolProvider.d.ts.map +1 -0
- package/dist/services/tool-providers/TIToolProvider.js +123 -0
- package/dist/services/tool-providers/TIToolProvider.js.map +1 -0
- package/dist/services/tool-providers/TTSToolProvider.d.ts +143 -0
- package/dist/services/tool-providers/TTSToolProvider.d.ts.map +1 -0
- package/dist/services/tool-providers/TTSToolProvider.js +168 -0
- package/dist/services/tool-providers/TTSToolProvider.js.map +1 -0
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +165 -0
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts.map +1 -0
- package/dist/services/tool-providers/ToolProviderRegistry.js +240 -0
- package/dist/services/tool-providers/ToolProviderRegistry.js.map +1 -0
- package/dist/services/tool-providers/index.d.ts +18 -0
- package/dist/services/tool-providers/index.d.ts.map +1 -0
- package/dist/services/tool-providers/index.js +15 -0
- package/dist/services/tool-providers/index.js.map +1 -0
- package/dist/services/tts/browser-provider.d.ts +24 -0
- package/dist/services/tts/browser-provider.d.ts.map +1 -0
- package/dist/services/tts/browser-provider.js +177 -0
- package/dist/services/tts/browser-provider.js.map +1 -0
- package/dist/services/tts/provider-interface.d.ts +131 -0
- package/dist/services/tts/provider-interface.d.ts.map +1 -0
- package/dist/services/tts/provider-interface.js +10 -0
- package/dist/services/tts/provider-interface.js.map +1 -0
- package/dist/tools/calculators/desmos-provider.d.ts +54 -0
- package/dist/tools/calculators/desmos-provider.d.ts.map +1 -0
- package/dist/tools/calculators/desmos-provider.js +384 -0
- package/dist/tools/calculators/desmos-provider.js.map +1 -0
- package/dist/tools/calculators/mathjs-provider.d.ts +50 -0
- package/dist/tools/calculators/mathjs-provider.d.ts.map +1 -0
- package/dist/tools/calculators/mathjs-provider.js +660 -0
- package/dist/tools/calculators/mathjs-provider.js.map +1 -0
- package/dist/tools/calculators/ti-provider.d.ts +122 -0
- package/dist/tools/calculators/ti-provider.d.ts.map +1 -0
- package/dist/tools/calculators/ti-provider.js +587 -0
- package/dist/tools/calculators/ti-provider.js.map +1 -0
- package/dist/tools/client.d.ts +17 -0
- package/dist/tools/client.d.ts.map +1 -0
- package/dist/tools/client.js +26 -0
- package/dist/tools/client.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +10 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/library-loader.d.ts +63 -0
- package/dist/tools/library-loader.d.ts.map +1 -0
- package/dist/tools/library-loader.js +292 -0
- package/dist/tools/library-loader.js.map +1 -0
- package/dist/tools/response-discovery.d.ts +72 -0
- package/dist/tools/response-discovery.d.ts.map +1 -0
- package/dist/tools/response-discovery.js +183 -0
- package/dist/tools/response-discovery.js.map +1 -0
- package/dist/tools/tool-coordinator.d.ts +76 -0
- package/dist/tools/tool-coordinator.d.ts.map +1 -0
- package/dist/tools/tool-coordinator.js +197 -0
- package/dist/tools/tool-coordinator.js.map +1 -0
- package/dist/tools/types.d.ts +427 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +13 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/variant-resolver.d.ts +48 -0
- package/dist/tools/variant-resolver.d.ts.map +1 -0
- package/dist/tools/variant-resolver.js +214 -0
- package/dist/tools/variant-resolver.js.map +1 -0
- package/dist/types/events.d.ts +157 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +11 -0
- package/dist/types/events.js.map +1 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +11 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +71 -0
- package/src/README.md +626 -0
- package/src/components/QuestionToolBar.svelte +406 -0
- package/src/player/AssessmentLayout.svelte +68 -0
- package/src/player/README.md +154 -0
- package/src/reference-layout/README.md +135 -0
- package/src/reference-layout/ReferenceLayout.svelte +209 -0
- package/src/reference-layout/components/AssessmentContent.svelte +259 -0
- package/src/reference-layout/components/AssessmentFooter.svelte +89 -0
- package/src/reference-layout/components/AssessmentHeader.svelte +250 -0
- package/src/reference-layout/components/AssessmentNavigation.svelte +134 -0
- package/src/reference-layout/components/AssessmentToolsBar.svelte +176 -0
- package/src/reference-layout/components/ItemPanel.svelte +200 -0
- package/src/reference-layout/components/NotesPanel.svelte +179 -0
- package/src/reference-layout/components/PassagePanel.svelte +76 -0
- package/src/tools/README.md +616 -0
- package/src/tools/calculators/README.md +395 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Reference Layout
|
|
2
|
+
|
|
3
|
+
A reference assessment layout implementation for PIE assessments.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This directory contains a complete layout implementation demonstrating a production-ready assessment interface. It provides:
|
|
8
|
+
|
|
9
|
+
- Three-column layout (passage | item | notes)
|
|
10
|
+
- Top bar with accommodations and assessment controls
|
|
11
|
+
- Navigation bar with question selector and numbered navigation
|
|
12
|
+
- Bottom bar with item-level tools
|
|
13
|
+
- Item-specific inline tools (answer eliminator, notes)
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
This layout is **presentation-only** - it orchestrates visual layout but delegates all business logic to:
|
|
18
|
+
- `AssessmentPlayer` for navigation and state management
|
|
19
|
+
- Assessment Toolkit services (ToolCoordinator, TTSService, etc.)
|
|
20
|
+
- Existing tool components from `/lib/tags/tool-*`
|
|
21
|
+
|
|
22
|
+
## Components
|
|
23
|
+
|
|
24
|
+
### Main Component
|
|
25
|
+
|
|
26
|
+
- **ReferenceLayout.svelte** - Main layout orchestrator
|
|
27
|
+
|
|
28
|
+
### Sub-Components
|
|
29
|
+
|
|
30
|
+
- **AssessmentHeader.svelte** - Top bar with accommodations and assessment actions
|
|
31
|
+
- **AssessmentNavigation.svelte** - Question selector and numbered navigation
|
|
32
|
+
- **AssessmentContent.svelte** - Three-column content container
|
|
33
|
+
- **PassagePanel.svelte** - Left column for passages (optional)
|
|
34
|
+
- **ItemPanel.svelte** - Middle column for questions/items
|
|
35
|
+
- **NotesPanel.svelte** - Right sidebar for notes (optional)
|
|
36
|
+
- **AssessmentToolsBar.svelte** - Bottom bar with item-level tools
|
|
37
|
+
- **AssessmentFooter.svelte** - Footer with navigation and actions
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### Direct Usage
|
|
42
|
+
|
|
43
|
+
```svelte
|
|
44
|
+
<script>
|
|
45
|
+
import { AssessmentPlayer } from '$lib/assessment-toolkit/reference-implementation';
|
|
46
|
+
import { ReferenceLayout } from '$lib/assessment-toolkit/reference-layout';
|
|
47
|
+
|
|
48
|
+
const player = new AssessmentPlayer({
|
|
49
|
+
assessment,
|
|
50
|
+
organizationId,
|
|
51
|
+
bundleHost,
|
|
52
|
+
mode: 'gather',
|
|
53
|
+
role: 'student',
|
|
54
|
+
// ...
|
|
55
|
+
});
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<ReferenceLayout {player} {assessment} />
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Via Reference Implementation
|
|
62
|
+
|
|
63
|
+
The reference implementation provides a wrapper that uses this layout:
|
|
64
|
+
|
|
65
|
+
```svelte
|
|
66
|
+
<script>
|
|
67
|
+
import { ReferenceAssessmentLayout } from '$lib/assessment-toolkit/reference-implementation';
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<ReferenceAssessmentLayout {assessment} {organizationId} />
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Key Features
|
|
74
|
+
|
|
75
|
+
### Tool Placement
|
|
76
|
+
|
|
77
|
+
Tools are visually grouped into different areas but use the same interface:
|
|
78
|
+
|
|
79
|
+
1. **Accommodations (Top Right)**
|
|
80
|
+
- Audio (TTS)
|
|
81
|
+
- Translation
|
|
82
|
+
- Contrast (Color Scheme)
|
|
83
|
+
- Fullscreen
|
|
84
|
+
|
|
85
|
+
2. **Assessment Actions (Top Center)**
|
|
86
|
+
- Overall Instructions
|
|
87
|
+
- Resources
|
|
88
|
+
- Review Summary
|
|
89
|
+
- Finish Later
|
|
90
|
+
- Flag Question
|
|
91
|
+
|
|
92
|
+
3. **Item-Level Tools (Bottom Bar)**
|
|
93
|
+
- Calculator
|
|
94
|
+
- Graph
|
|
95
|
+
- Periodic Table
|
|
96
|
+
- Protractor
|
|
97
|
+
- Line Reader
|
|
98
|
+
- Text Magnifier
|
|
99
|
+
- Ruler
|
|
100
|
+
|
|
101
|
+
4. **Item-Specific (Inline)**
|
|
102
|
+
- Answer Eliminator (inline with choices)
|
|
103
|
+
- Notes (right sidebar)
|
|
104
|
+
|
|
105
|
+
### Passage Support
|
|
106
|
+
|
|
107
|
+
- Automatically detects passages from items
|
|
108
|
+
- Loads passages on demand
|
|
109
|
+
- Renders passages in left column using `<pie-iife-player>`
|
|
110
|
+
|
|
111
|
+
### Responsive Design
|
|
112
|
+
|
|
113
|
+
- Desktop: Full three-column layout
|
|
114
|
+
- Tablet: Hides notes column, two-column layout
|
|
115
|
+
- Mobile: Single column with tools in menu
|
|
116
|
+
|
|
117
|
+
## Dependencies
|
|
118
|
+
|
|
119
|
+
This layout requires:
|
|
120
|
+
|
|
121
|
+
- `AssessmentPlayer` from `reference-implementation`
|
|
122
|
+
- Assessment Toolkit services (ToolCoordinator, TTSService, etc.)
|
|
123
|
+
- Existing tool components from `/lib/tags/tool-*`
|
|
124
|
+
- `<pie-iife-player>` for rendering items and passages
|
|
125
|
+
|
|
126
|
+
## Status
|
|
127
|
+
|
|
128
|
+
🚧 **Under development** - Implementation in progress. See [LAYOUT-PLAN.md](./LAYOUT-PLAN.md) for detailed implementation plan.
|
|
129
|
+
|
|
130
|
+
## Related Documentation
|
|
131
|
+
|
|
132
|
+
- [LAYOUT-PLAN.md](./LAYOUT-PLAN.md) - Detailed implementation plan
|
|
133
|
+
- [../reference-implementation/README.md](../reference-implementation/README.md) - Reference implementation documentation
|
|
134
|
+
- [../../docs/tools-high-level-architecture.md](../../docs/tools-high-level-architecture.md) - Tools architecture overview
|
|
135
|
+
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
|
|
3
|
+
import type { AssessmentEntity, ItemConfig, ItemEntity, PassageEntity, RubricBlock } from '@pie-players/pie-players-shared/types';
|
|
4
|
+
import ToolAnnotationToolbar from '@pie-players/pie-tool-annotation-toolbar';
|
|
5
|
+
import { onDestroy, onMount } from 'svelte';
|
|
6
|
+
import type { AssessmentPlayer } from '../player/AssessmentPlayer';
|
|
7
|
+
import AssessmentContent from './components/AssessmentContent.svelte';
|
|
8
|
+
import AssessmentFooter from './components/AssessmentFooter.svelte';
|
|
9
|
+
import AssessmentHeader from './components/AssessmentHeader.svelte';
|
|
10
|
+
import AssessmentNavigation from './components/AssessmentNavigation.svelte';
|
|
11
|
+
import AssessmentToolsBar from './components/AssessmentToolsBar.svelte';
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
player,
|
|
15
|
+
assessment,
|
|
16
|
+
bundleHost,
|
|
17
|
+
organizationId,
|
|
18
|
+
userName
|
|
19
|
+
}: {
|
|
20
|
+
player: AssessmentPlayer;
|
|
21
|
+
assessment: AssessmentEntity;
|
|
22
|
+
bundleHost?: string;
|
|
23
|
+
organizationId?: string | null;
|
|
24
|
+
userName?: string;
|
|
25
|
+
} = $props();
|
|
26
|
+
|
|
27
|
+
// State from player
|
|
28
|
+
let currentItem = $state<ItemEntity | null>(null);
|
|
29
|
+
let currentConfig = $state<ItemConfig | null>(null);
|
|
30
|
+
let sessionState = $state<{ id: string; data: any[] }>({ id: '', data: [] });
|
|
31
|
+
let navigationState = $state({
|
|
32
|
+
currentIndex: 0,
|
|
33
|
+
totalItems: 0,
|
|
34
|
+
canNext: false,
|
|
35
|
+
canPrevious: false,
|
|
36
|
+
isLoading: true
|
|
37
|
+
});
|
|
38
|
+
let isLoadingItem = $state(true);
|
|
39
|
+
let passage = $state<PassageEntity | null>(null);
|
|
40
|
+
let rubricBlocks = $state<RubricBlock[]>([]);
|
|
41
|
+
|
|
42
|
+
// Picture Dictionary Modal state
|
|
43
|
+
let showPictureDictionaryModal = $state(false);
|
|
44
|
+
let pictureDictionaryWord = $state('');
|
|
45
|
+
|
|
46
|
+
// Annotation toolbar event handlers
|
|
47
|
+
function handleDictionaryLookup(detail: { text: string }) {
|
|
48
|
+
// TODO: Open dictionary modal
|
|
49
|
+
console.log('Dictionary lookup:', detail.text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function handleTranslationRequest(detail: { text: string }) {
|
|
53
|
+
// TODO: Open translation modal
|
|
54
|
+
console.log('Translation request:', detail.text);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function handlePictureDictionaryLookup(detail: { text: string }) {
|
|
58
|
+
pictureDictionaryWord = detail.text;
|
|
59
|
+
showPictureDictionaryModal = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Load passage when item changes
|
|
63
|
+
async function loadPassage(item: ItemEntity | null) {
|
|
64
|
+
if (!item?.passage) {
|
|
65
|
+
passage = null;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If passage is already a full entity, use it
|
|
70
|
+
if (typeof item.passage === 'object' && 'id' in item.passage) {
|
|
71
|
+
passage = item.passage as PassageEntity;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Otherwise load by ID
|
|
76
|
+
if (typeof item.passage === 'string') {
|
|
77
|
+
try {
|
|
78
|
+
const url = organizationId
|
|
79
|
+
? `/api/passage/${item.passage}?organizationId=${encodeURIComponent(organizationId)}`
|
|
80
|
+
: `/api/passage/${item.passage}`;
|
|
81
|
+
const response = await fetch(url);
|
|
82
|
+
if (response.ok) {
|
|
83
|
+
passage = await response.json();
|
|
84
|
+
} else {
|
|
85
|
+
passage = null;
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('[ReferenceLayout] Error loading passage:', error);
|
|
89
|
+
passage = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Subscribe to player state changes
|
|
95
|
+
let unsubNav: (() => void) | null = null;
|
|
96
|
+
let unsubItem: (() => void) | null = null;
|
|
97
|
+
let unsubLoading: (() => void) | null = null;
|
|
98
|
+
let unsubSession: (() => void) | null = null;
|
|
99
|
+
|
|
100
|
+
onMount(() => {
|
|
101
|
+
unsubNav = player.onNavigationChange((state) => {
|
|
102
|
+
navigationState = state;
|
|
103
|
+
rubricBlocks =
|
|
104
|
+
player
|
|
105
|
+
.getCurrentSectionRubricBlocks()
|
|
106
|
+
.filter((b) => b.view === 'candidate');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
unsubItem = player.onItemChange((item) => {
|
|
110
|
+
currentItem = item;
|
|
111
|
+
currentConfig = item?.config as ItemConfig | null;
|
|
112
|
+
loadPassage(item);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
unsubLoading = player.onLoadingChange((loading) => {
|
|
116
|
+
isLoadingItem = loading;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
unsubSession = player.onSessionChange((session) => {
|
|
120
|
+
sessionState = session;
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
onDestroy(() => {
|
|
125
|
+
unsubNav?.();
|
|
126
|
+
unsubItem?.();
|
|
127
|
+
unsubLoading?.();
|
|
128
|
+
unsubSession?.();
|
|
129
|
+
});
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<div class="reference-layout">
|
|
133
|
+
<!-- Header -->
|
|
134
|
+
<AssessmentHeader
|
|
135
|
+
{player}
|
|
136
|
+
{assessment}
|
|
137
|
+
currentQuestion={navigationState.currentIndex + 1}
|
|
138
|
+
totalQuestions={navigationState.totalItems}
|
|
139
|
+
{userName}
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
<!-- Navigation -->
|
|
143
|
+
<AssessmentNavigation
|
|
144
|
+
{player}
|
|
145
|
+
currentIndex={navigationState.currentIndex}
|
|
146
|
+
totalItems={navigationState.totalItems}
|
|
147
|
+
/>
|
|
148
|
+
|
|
149
|
+
<!-- Main Content -->
|
|
150
|
+
<AssessmentContent
|
|
151
|
+
{player}
|
|
152
|
+
currentItem={currentItem}
|
|
153
|
+
config={currentConfig}
|
|
154
|
+
{passage}
|
|
155
|
+
{rubricBlocks}
|
|
156
|
+
session={sessionState}
|
|
157
|
+
{bundleHost}
|
|
158
|
+
{organizationId}
|
|
159
|
+
isLoading={isLoadingItem}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
<!-- Tools Bar -->
|
|
163
|
+
<AssessmentToolsBar {player} />
|
|
164
|
+
|
|
165
|
+
<!-- Footer -->
|
|
166
|
+
<AssessmentFooter
|
|
167
|
+
{player}
|
|
168
|
+
currentQuestion={navigationState.currentIndex + 1}
|
|
169
|
+
totalQuestions={navigationState.totalItems}
|
|
170
|
+
canNext={navigationState.canNext}
|
|
171
|
+
canPrevious={navigationState.canPrevious}
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<!-- Annotation Toolbar (floating, appears on text selection) -->
|
|
176
|
+
<!-- Outside layout container to avoid overflow: hidden affecting fixed positioning -->
|
|
177
|
+
<ToolAnnotationToolbar
|
|
178
|
+
enabled={true}
|
|
179
|
+
ttsService={player.getTTSService()}
|
|
180
|
+
highlightCoordinator={player.getHighlightCoordinator()}
|
|
181
|
+
ondictionarylookup={handleDictionaryLookup}
|
|
182
|
+
ontranslationrequest={handleTranslationRequest}
|
|
183
|
+
onpicturedictionarylookup={handlePictureDictionaryLookup}
|
|
184
|
+
/>
|
|
185
|
+
|
|
186
|
+
<!-- Picture Dictionary Modal -->
|
|
187
|
+
<PictureDictionaryModal
|
|
188
|
+
bind:visible={showPictureDictionaryModal}
|
|
189
|
+
word={pictureDictionaryWord}
|
|
190
|
+
organizationId={organizationId || undefined}
|
|
191
|
+
/>
|
|
192
|
+
|
|
193
|
+
<style>
|
|
194
|
+
.reference-layout {
|
|
195
|
+
display: grid;
|
|
196
|
+
grid-template-rows: auto auto 1fr auto auto;
|
|
197
|
+
grid-template-columns: 1fr;
|
|
198
|
+
height: 100%;
|
|
199
|
+
min-height: 0;
|
|
200
|
+
overflow: hidden;
|
|
201
|
+
background-color: var(--pie-background, #ffffff);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Ensure grid rows don't overflow */
|
|
205
|
+
:global(.reference-layout > *) {
|
|
206
|
+
min-height: 0;
|
|
207
|
+
}
|
|
208
|
+
</style>
|
|
209
|
+
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
|
|
3
|
+
import type { ItemConfig, ItemEntity, PassageEntity, RubricBlock } from '@pie-players/pie-players-shared/types';
|
|
4
|
+
import { onDestroy, onMount } from 'svelte';
|
|
5
|
+
import type { AssessmentPlayer } from '../../player/AssessmentPlayer';
|
|
6
|
+
import ItemPanel from './ItemPanel.svelte';
|
|
7
|
+
import NotesPanel from './NotesPanel.svelte';
|
|
8
|
+
import PassagePanel from './PassagePanel.svelte';
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
player,
|
|
12
|
+
currentItem,
|
|
13
|
+
config,
|
|
14
|
+
passage,
|
|
15
|
+
rubricBlocks = [],
|
|
16
|
+
session,
|
|
17
|
+
bundleHost,
|
|
18
|
+
organizationId,
|
|
19
|
+
isLoading = false
|
|
20
|
+
}: {
|
|
21
|
+
player: AssessmentPlayer;
|
|
22
|
+
currentItem: ItemEntity | null;
|
|
23
|
+
config: ItemConfig | null;
|
|
24
|
+
passage: PassageEntity | null;
|
|
25
|
+
rubricBlocks?: RubricBlock[];
|
|
26
|
+
session: { id: string; data: any[] };
|
|
27
|
+
bundleHost?: string;
|
|
28
|
+
organizationId?: string | null;
|
|
29
|
+
isLoading?: boolean;
|
|
30
|
+
} = $props();
|
|
31
|
+
|
|
32
|
+
let containerEl = $state<HTMLDivElement | null>(null);
|
|
33
|
+
let passagePanelEl = $state<HTMLDivElement | null>(null);
|
|
34
|
+
let itemPanelEl = $state<HTMLDivElement | null>(null);
|
|
35
|
+
let dividerEl = $state<HTMLDivElement | null>(null);
|
|
36
|
+
let notesDividerEl = $state<HTMLDivElement | null>(null);
|
|
37
|
+
|
|
38
|
+
// Resize state
|
|
39
|
+
let isDragging = $state(false);
|
|
40
|
+
let startX = $state(0);
|
|
41
|
+
let passageWidth = $state(300); // Initial width in pixels
|
|
42
|
+
let minPassageWidth = 200;
|
|
43
|
+
let maxPassageWidth = 800;
|
|
44
|
+
|
|
45
|
+
const hasPassage = $derived(
|
|
46
|
+
(passage !== null) || (rubricBlocks?.length ?? 0) > 0,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Notes sidebar
|
|
50
|
+
let showNotes = $state(true);
|
|
51
|
+
const notesWidth = 320;
|
|
52
|
+
|
|
53
|
+
// Calculate grid columns dynamically
|
|
54
|
+
const gridColumns = $derived.by(() => {
|
|
55
|
+
const parts: string[] = [];
|
|
56
|
+
|
|
57
|
+
if (hasPassage) {
|
|
58
|
+
parts.push(`${passageWidth}px`, 'auto');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Item column always present
|
|
62
|
+
parts.push('1fr');
|
|
63
|
+
|
|
64
|
+
// Notes column (optional)
|
|
65
|
+
if (showNotes) {
|
|
66
|
+
parts.push('auto', `${notesWidth}px`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return parts.join(' ');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
function handleDividerMouseDown(e: MouseEvent) {
|
|
73
|
+
if (!hasPassage) return;
|
|
74
|
+
isDragging = true;
|
|
75
|
+
startX = e.clientX;
|
|
76
|
+
document.body.style.cursor = 'col-resize';
|
|
77
|
+
document.body.style.userSelect = 'none';
|
|
78
|
+
|
|
79
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
80
|
+
window.addEventListener('mouseup', handleMouseUp);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handleMouseMove(e: MouseEvent) {
|
|
84
|
+
if (!isDragging || !containerEl) return;
|
|
85
|
+
|
|
86
|
+
const deltaX = e.clientX - startX;
|
|
87
|
+
const containerRect = containerEl.getBoundingClientRect();
|
|
88
|
+
const newWidth = passageWidth + deltaX;
|
|
89
|
+
const constrainedWidth = Math.max(
|
|
90
|
+
minPassageWidth,
|
|
91
|
+
Math.min(maxPassageWidth, newWidth)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
passageWidth = constrainedWidth;
|
|
95
|
+
startX = e.clientX;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleMouseUp() {
|
|
99
|
+
isDragging = false;
|
|
100
|
+
document.body.style.cursor = '';
|
|
101
|
+
document.body.style.userSelect = '';
|
|
102
|
+
|
|
103
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
104
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
108
|
+
if (!hasPassage) return;
|
|
109
|
+
|
|
110
|
+
const step = 10; // pixels per keypress
|
|
111
|
+
let deltaX = 0;
|
|
112
|
+
|
|
113
|
+
if (e.key === 'ArrowLeft') {
|
|
114
|
+
deltaX = -step;
|
|
115
|
+
} else if (e.key === 'ArrowRight') {
|
|
116
|
+
deltaX = step;
|
|
117
|
+
} else {
|
|
118
|
+
return; // Ignore other keys
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
const newWidth = passageWidth + deltaX;
|
|
123
|
+
const constrainedWidth = Math.max(
|
|
124
|
+
minPassageWidth,
|
|
125
|
+
Math.min(maxPassageWidth, newWidth)
|
|
126
|
+
);
|
|
127
|
+
passageWidth = constrainedWidth;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
onMount(() => {
|
|
131
|
+
// Persist width to localStorage
|
|
132
|
+
const savedWidth = localStorage.getItem('schoolcity-passage-width');
|
|
133
|
+
if (savedWidth) {
|
|
134
|
+
passageWidth = parseInt(savedWidth, 10);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
onDestroy(() => {
|
|
139
|
+
// Save width to localStorage
|
|
140
|
+
localStorage.setItem('schoolcity-passage-width', String(passageWidth));
|
|
141
|
+
handleMouseUp(); // Clean up any active drag
|
|
142
|
+
});
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<div class="assessment-content" bind:this={containerEl} style="grid-template-columns: {gridColumns}">
|
|
146
|
+
{#if hasPassage}
|
|
147
|
+
<!-- Passage Panel -->
|
|
148
|
+
<div class="passage-container" bind:this={passagePanelEl}>
|
|
149
|
+
<PassagePanel {passage} {rubricBlocks} {bundleHost} env={player.getEnv()} />
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<!-- Resizable Divider -->
|
|
153
|
+
<div
|
|
154
|
+
class="divider"
|
|
155
|
+
class:dragging={isDragging}
|
|
156
|
+
bind:this={dividerEl}
|
|
157
|
+
onmousedown={handleDividerMouseDown}
|
|
158
|
+
onkeydown={handleKeyDown}
|
|
159
|
+
role="button"
|
|
160
|
+
aria-label="Resize passage panel"
|
|
161
|
+
tabindex="0"
|
|
162
|
+
>
|
|
163
|
+
<div class="divider-handle"></div>
|
|
164
|
+
</div>
|
|
165
|
+
{/if}
|
|
166
|
+
|
|
167
|
+
<!-- Item Panel -->
|
|
168
|
+
<div class="item-container" bind:this={itemPanelEl}>
|
|
169
|
+
<ItemPanel
|
|
170
|
+
{currentItem}
|
|
171
|
+
{config}
|
|
172
|
+
{session}
|
|
173
|
+
{bundleHost}
|
|
174
|
+
env={player.getEnv()}
|
|
175
|
+
toolCoordinator={player.getToolCoordinator()}
|
|
176
|
+
{isLoading}
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{#if showNotes}
|
|
181
|
+
<!-- Notes Divider (visual only for now) -->
|
|
182
|
+
<div class="divider notes-divider" bind:this={notesDividerEl} aria-hidden="true">
|
|
183
|
+
<div class="divider-handle"></div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<!-- Notes Panel -->
|
|
187
|
+
<div class="notes-container">
|
|
188
|
+
<NotesPanel
|
|
189
|
+
itemId={currentItem?.id || ''}
|
|
190
|
+
itemLabel={currentItem?.name || ''}
|
|
191
|
+
onClose={() => (showNotes = false)}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
{/if}
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<style>
|
|
198
|
+
.assessment-content {
|
|
199
|
+
display: grid;
|
|
200
|
+
gap: 0;
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
position: relative;
|
|
203
|
+
min-height: 0;
|
|
204
|
+
height: 100%;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.passage-container,
|
|
208
|
+
.item-container,
|
|
209
|
+
.notes-container {
|
|
210
|
+
overflow-y: auto;
|
|
211
|
+
overflow-x: hidden;
|
|
212
|
+
background-color: var(--pie-background, #ffffff);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.divider {
|
|
216
|
+
position: relative;
|
|
217
|
+
width: 4px;
|
|
218
|
+
background-color: var(--pie-border, #e0e0e0);
|
|
219
|
+
cursor: col-resize;
|
|
220
|
+
display: flex;
|
|
221
|
+
align-items: center;
|
|
222
|
+
justify-content: center;
|
|
223
|
+
transition: background-color 0.2s;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.divider:hover,
|
|
227
|
+
.divider.dragging {
|
|
228
|
+
background-color: var(--pie-primary, #3f51b5);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.divider-handle {
|
|
232
|
+
width: 2px;
|
|
233
|
+
height: 40px;
|
|
234
|
+
background-color: var(--pie-text-secondary, #666);
|
|
235
|
+
border-radius: 1px;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.divider.dragging .divider-handle {
|
|
239
|
+
background-color: var(--pie-primary, #3f51b5);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.divider:focus {
|
|
243
|
+
outline: 2px solid var(--pie-primary, #3f51b5);
|
|
244
|
+
outline-offset: -2px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.notes-divider {
|
|
248
|
+
cursor: default;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* On narrow screens, hide notes by default */
|
|
252
|
+
@media (max-width: 1100px) {
|
|
253
|
+
.notes-container,
|
|
254
|
+
.notes-divider {
|
|
255
|
+
display: none;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
</style>
|
|
259
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AssessmentPlayer } from '../../player/AssessmentPlayer';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
player,
|
|
6
|
+
currentQuestion,
|
|
7
|
+
totalQuestions,
|
|
8
|
+
canNext,
|
|
9
|
+
canPrevious
|
|
10
|
+
}: {
|
|
11
|
+
player: AssessmentPlayer;
|
|
12
|
+
currentQuestion: number;
|
|
13
|
+
totalQuestions: number;
|
|
14
|
+
canNext: boolean;
|
|
15
|
+
canPrevious: boolean;
|
|
16
|
+
} = $props();
|
|
17
|
+
|
|
18
|
+
function handlePrevious() {
|
|
19
|
+
player.navigatePrevious();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function handleNext() {
|
|
23
|
+
player.navigateNext();
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<footer class="assessment-footer">
|
|
28
|
+
<!-- Question Counter and Navigation -->
|
|
29
|
+
<div class="footer-content">
|
|
30
|
+
<span class="question-counter">Question {currentQuestion} of {totalQuestions}</span>
|
|
31
|
+
<div class="navigation-buttons">
|
|
32
|
+
<button class="nav-button" onclick={handlePrevious} disabled={!canPrevious}>
|
|
33
|
+
← Back
|
|
34
|
+
</button>
|
|
35
|
+
<button class="nav-button" onclick={handleNext} disabled={!canNext}>
|
|
36
|
+
Next →
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</footer>
|
|
41
|
+
|
|
42
|
+
<style>
|
|
43
|
+
.assessment-footer {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
padding: 0.75rem 1rem;
|
|
48
|
+
background-color: var(--pie-background, #ffffff);
|
|
49
|
+
border-top: 1px solid var(--pie-border, #e0e0e0);
|
|
50
|
+
min-height: 56px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.footer-content {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 1rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.question-counter {
|
|
60
|
+
font-size: 0.875rem;
|
|
61
|
+
color: var(--pie-text, #000);
|
|
62
|
+
font-weight: 500;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.navigation-buttons {
|
|
66
|
+
display: flex;
|
|
67
|
+
gap: 0.5rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.nav-button {
|
|
71
|
+
padding: 0.5rem 1rem;
|
|
72
|
+
background-color: var(--pie-background, #ffffff);
|
|
73
|
+
border: 1px solid var(--pie-border, #e0e0e0);
|
|
74
|
+
border-radius: 4px;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
font-size: 0.875rem;
|
|
77
|
+
color: var(--pie-text, #000);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.nav-button:hover:not(:disabled) {
|
|
81
|
+
background-color: var(--pie-secondary-background, #f5f5f5);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.nav-button:disabled {
|
|
85
|
+
opacity: 0.5;
|
|
86
|
+
cursor: not-allowed;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
89
|
+
|