-
Notifications
You must be signed in to change notification settings - Fork 627
OCV first pass #10355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OCV first pass #10355
Changes from 15 commits
d31eca8
f55fca2
c705f96
87236db
0593552
5259143
9354dc2
a764d25
6446b2a
2120588
4da2fdc
05d4be7
816c662
8336f42
bab52a2
f905236
4a4eb79
6c86476
cd43ef9
be206b5
2bb7a05
b209592
9438c18
158939c
c714b42
728ebd8
69ec94d
9dc5367
3e8c6ba
83b45f8
980020a
3b6a3ff
943c493
36c823c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -805,6 +805,7 @@ declare namespace pxt.editor { | |
| extensionsVisible?: boolean; | ||
| isMultiplayerGame?: boolean; // Arcade: Does the current project contain multiplayer blocks? | ||
| onboarding?: pxt.tour.BubbleStep[]; | ||
| feedback?: boolean; | ||
|
||
| } | ||
|
|
||
| export interface EditorState { | ||
|
|
@@ -1047,6 +1048,7 @@ declare namespace pxt.editor { | |
| showLanguagePicker(): void; | ||
| showShareDialog(title?: string, kind?: "multiplayer" | "vscode" | "share"): void; | ||
| showAboutDialog(): void; | ||
| showFeedbackDialog(): void; | ||
| showTurnBackTimeDialogAsync(): Promise<void>; | ||
|
|
||
| showLoginDialog(continuationHash?: string): void; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import { useEffect } from "react" | ||
| import { initFeedbackEventListener, removeFeedbackEventListener } from "./FeedbackEventListener"; | ||
| import { baseConfig, ratingFeedbackConfig, appId, feedbackFrameUrl } from "./configs"; | ||
| import { Modal } from "../Modal"; | ||
|
|
||
| // both components require onClose because the feedback modal should close when the user clicks the "finish" button | ||
| // this would not happen if the EventListener did not have a callback to close the modal | ||
| interface IFeedbackModalProps { | ||
| feedbackConfig: any; | ||
| frameId: string; | ||
| title: string; | ||
| onClose: () => void; | ||
| } | ||
|
|
||
| // right now, there are two kinds of feedback that I think could be valuable for our targets | ||
| // generic and rating feedback, but we will likely want to expand this | ||
| interface IFeedbackProps { | ||
| kind: "generic" | "rating"; | ||
| onClose: () => void; | ||
| } | ||
|
|
||
| // Wrapper component of the feedback modal so kind can determine what feedback actually shows in the modal | ||
| export const Feedback = (props: IFeedbackProps) => { | ||
| const { kind, onClose } = props; | ||
| return ( | ||
| <> | ||
| {kind === "generic" && | ||
| <FeedbackModal | ||
| feedbackConfig={baseConfig} | ||
| frameId="menu-feedback-frame" | ||
| title={lf("Leave Feedback")} | ||
| onClose={onClose} | ||
| />} | ||
| {kind === "rating" && | ||
| <FeedbackModal | ||
| feedbackConfig={ratingFeedbackConfig} | ||
| frameId="activity-feedback-frame" | ||
| title={lf("Rate this activity")} | ||
| onClose={onClose} /> | ||
| } | ||
| </> | ||
| ) | ||
| } | ||
|
|
||
| export const FeedbackModal = (props: IFeedbackModalProps) => { | ||
| const { feedbackConfig, frameId, title, onClose } = props; | ||
|
|
||
| const onDismiss = () => { | ||
| onClose(); | ||
| } | ||
|
|
||
| let callbacks = { onDismiss }; | ||
|
|
||
| useEffect(() => { | ||
| initFeedbackEventListener(feedbackConfig, frameId, callbacks); | ||
| return () => { | ||
| removeFeedbackEventListener(); | ||
| } | ||
| }, []) | ||
| return ( | ||
| <Modal className="feedback-modal" title={title} onClose={onClose}> | ||
| <iframe | ||
| title="feedback" | ||
| height="450px" | ||
| width="550px" | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| id={frameId} | ||
| src={`${feedbackFrameUrl}/centrohost?appname=ocvfeedback&feature=host-ocv-inapp-feedback&platform=web&appId=${appId}#/hostedpage`} | ||
| sandbox="allow-scripts allow-same-origin allow-forms allow-popups" /> | ||
| </Modal> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| import { appId, feedbackFrameUrl } from './configs'; | ||
| interface FeedbackRequestEventPayload<T> { | ||
| Event: string | ||
| EventArgs: string | ||
| } | ||
| type FeedbackRequestPayloadType = FeedbackRequestEventPayload<any> | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| interface FeedbackResponseEventPayload<T> { | ||
| event: string | ||
| data: T | ||
| error?: any | ||
| } | ||
|
|
||
| // for styling the feedback, we use this object. It is mostly used to change the colors. | ||
| // we'll want to change this based on the target and whether high contrast is enabled | ||
| let themeOptions = { | ||
| baseTheme: "PublisherLightTheme", | ||
| } | ||
|
|
||
| let initfeedbackOptions: any; | ||
| let feedbackData: any; | ||
| let FEEDBACK_FRAME_ID: string; | ||
| let currentTheme = ''; | ||
| let feedbackCallbacks: any; | ||
|
|
||
| // the function to initialize the feedback event listener | ||
| // feedbackConfig: needs to be passed in as a prop because the things that | ||
| /** | ||
| * The function to initialize the feedback event listener | ||
| * @param {any} feedbackConfig: the feedback config object whose fields are defined in OCV. | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * This changes based on what type of feedback we want to collect. Look at configs.ts for more details. | ||
| * @param {string} frameId: the html id of the actual iframe where the feedback will be displayed | ||
| * @param {any} [callbacks]: an object of functions that can be called when certain events happen in the feedback modal. | ||
| * Needs to be passed in because the callbacks will depend on what the parent wants to react to. | ||
| */ | ||
| export const initFeedbackEventListener = (feedbackConfig: any, frameId: string, callbacks?: any) => { | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| window.addEventListener('message', feedbackCallbackEventListener); | ||
| feedbackCallbacks = callbacks; | ||
| initfeedbackOptions = { | ||
| appId: appId, | ||
| ageGroup: "Undefined", | ||
| authenticationType: "Unauthenticated", | ||
| clientName: "MakeCode", | ||
| feedbackConfig: feedbackConfig, | ||
| isProduction: false, | ||
| themeOptions: themeOptions, | ||
| // telemetry - will likely want this | ||
| } | ||
|
|
||
| feedbackData = initfeedbackOptions; | ||
| FEEDBACK_FRAME_ID = frameId; | ||
| } | ||
|
|
||
| export const removeFeedbackEventListener = () => { | ||
| window.removeEventListener('message', feedbackCallbackEventListener); | ||
| } | ||
|
|
||
| /** | ||
| * The function that listens for the feedback events. | ||
| * The events here are the ones that seemed most useful to log or respond to | ||
| * @param {MessageEvent<FeedbackRequestPayloadType>} event: the event received from OCV | ||
| */ | ||
| const feedbackCallbackEventListener = (event: MessageEvent<FeedbackRequestPayloadType>) => { | ||
srietkerk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (event.data.Event) { | ||
| const payload: FeedbackRequestPayloadType = event.data | ||
| switch (payload.Event) { | ||
| case 'InAppFeedbackInitOptions': //This is required to initialise feedback | ||
| sendFeedbackInitOptions() | ||
| break | ||
| case 'InAppFeedbackOnError': //Invoked when an error occurrs on feedback submission - would be nice to log something to the user | ||
| console.log('Error Message: ', payload.EventArgs) | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| break | ||
| case 'InAppFeedbackInitializationComplete': //Invoked when feedback form is fully initialised and displays error/warning if any - nice to have a log for this | ||
| console.log('InAppFeedbackInitializationComplete: ', payload.EventArgs) | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| break | ||
| case 'InAppFeedbackOnSuccess': //Invoked when feedback submission is successful - would be useful to have telemetry/something else on this event | ||
| console.log('InAppFeedbackOnSuccess: ', payload.EventArgs) | ||
| break | ||
| case 'InAppFeedbackDismissWithResult': //Invoked when feedback is dismissed - the big important one for us to be able to close the feedback modal | ||
| console.log('InAppFeedbackDismissWithResult: ', payload.EventArgs); | ||
| if (feedbackCallbacks.onDismiss) { | ||
| feedbackCallbacks.onDismiss(); | ||
| } | ||
| break | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // ***************** Helper Functions ***************** | ||
|
|
||
| // TODO | ||
| // haven't implemented yet with events, but this will be needed in order to update to high contrast | ||
| // general changes need to be made as well use the correct theme. the windows ones were just the defaults. | ||
| const sendUpdateTheme = () => { | ||
srietkerk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| type FeedbackResponsePayloadType = FeedbackResponseEventPayload<any> | ||
| if (currentTheme == 'WindowsDark') { | ||
| currentTheme = 'WindowsLight' | ||
| } else { | ||
| currentTheme = 'WindowsDark' | ||
| } | ||
| const response: FeedbackResponsePayloadType = { | ||
| event: 'OnFeedbackHostAppThemeChanged', | ||
| data: { | ||
| baseTheme: currentTheme, | ||
| }, | ||
| } | ||
| const iFrameEle = document.getElementById(FEEDBACK_FRAME_ID) as HTMLIFrameElement | ||
| iFrameEle!.contentWindow!.postMessage(response, feedbackFrameUrl) | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Actually initializes the feedback session. This is called when the feedback modal opens. | ||
| */ | ||
| const sendFeedbackInitOptions = () => { | ||
| type FeedbackResponsePayloadType = FeedbackResponseEventPayload<any> | ||
| feedbackData.callbackFunctions = undefined | ||
| let response: FeedbackResponsePayloadType = { | ||
| event: 'InAppFeedbackInitOptions', | ||
| data: feedbackData, | ||
| } | ||
| response = JSON.parse(JSON.stringify(response)) | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const iFrameEle = document.getElementById(FEEDBACK_FRAME_ID) as HTMLIFrameElement | ||
| iFrameEle!.contentWindow!.postMessage(response, feedbackFrameUrl) | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| export const appId = 50315; | ||
| export const feedbackFrameUrl = 'https://admin-ignite.microsoft.com'; | ||
|
||
|
|
||
| export const baseConfig: any = { | ||
| feedbackUiType: "NoSurface", | ||
| hostPlatform: "IFrame", | ||
| isDisplayed: true, | ||
| isEmailCollectionEnabled: false, // to enable email collection | ||
| isFileUploadEnabled: false, // to enable file upload function | ||
| isScreenshotEnabled: false, // to enable screenshot | ||
| isScreenRecordingEnabled: false, // to enable screen recording | ||
| invokeOnDismissOnEsc: false, | ||
| isFeedbackForumEnabled: false, | ||
| isMyFeedbackEnabled: false, | ||
| isThankYouPageDisabled: false, | ||
| } | ||
|
|
||
| export const ratingFeedbackConfig: any = { | ||
| ...baseConfig, | ||
| initialFeedbackType: "Unclassified", | ||
| scenarioConfig: { | ||
| isScenarioEnabled: true, | ||
| scenarioType: "Custom", | ||
| questionDetails: { | ||
| questionUiType: "Rating", | ||
| questionInstruction: { | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| displayedStringInEnglish: "What did you think of this activity?", | ||
| displayedString: "What did you think of this activity?" | ||
| }, | ||
| questionOptions: [ | ||
| { | ||
| displayedStringInEnglish: "Boring", | ||
| displayedString: "Boring" | ||
| }, | ||
| { | ||
| displayedStringInEnglish: "Not fun", | ||
| displayedString: "Not fun" | ||
| }, | ||
| { | ||
| displayedStringInEnglish: "Kinda fun", | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| displayedString: "Kinda fun" | ||
| }, | ||
| { | ||
| displayedStringInEnglish: "Fun", | ||
| displayedString: "Fun" | ||
| }, | ||
| { | ||
| displayedStringInEnglish: "Super fun", | ||
| displayedString: "Super fun" | ||
| }, | ||
| ], | ||
| "questionUiBehaviour": [ | ||
| "CommentNotRequired" | ||
| ] | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| .feedback-modal iframe{ | ||
srietkerk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| border: none; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.