Skip to content

Feature/tutorial setup #4

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

Merged
merged 12 commits into from
Jun 9, 2019
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
116 changes: 58 additions & 58 deletions src/editor/views/createWebview.ts → src/editor/ReactWebView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,116 +2,118 @@ import * as vscode from 'vscode'
import * as CR from 'typings'
import * as path from 'path'

import getNonce from './utils/nonce'
import onReceive from './onReceive'

/**
* Manages React webview panels
*/
class ReactPanel {
/**
* Track the currently panel. Only allow a single panel to exist at a time.
*/
public static currentPanel: ReactPanel | undefined = undefined
class ReactWebView {
// @ts-ignore
private panel: vscode.WebviewPanel
private extensionPath: string
private disposables: vscode.Disposable[] = []
private onReceive: any // TODO: properly type

public constructor(extensionPath: string) {
this.extensionPath = extensionPath

// Create and show a new webview panel
this.panel = this.createWebviewPanel(vscode.ViewColumn.One)

private readonly _panel: vscode.WebviewPanel
private readonly _extensionPath: string
private _disposables: vscode.Disposable[] = []
// Set the webview's initial html content
this.panel.webview.html = this.getHtmlForWebview()

public static async createOrShow(extensionPath: string): Promise<void> {
// const hasActiveEditor = vscode.window.activeTextEditor
// Listen for when the panel is disposed
// This happens when the user closes the panel or when the panel is closed programatically
this.panel.onDidDispose(() => this.dispose(), null, this.disposables)

// if (!hasActiveEditor) {
// throw new Error('Should have an open file on launch')
// }
const column = vscode.ViewColumn.One
// Handle messages from the webview
const onReceive = (action: string | CR.Action) => vscode.commands.executeCommand('coderoad.receive_action', action)
this.panel.webview.onDidReceiveMessage(onReceive, null, this.disposables)
console.log('webview loaded')
}

public async createOrShow(column: number): Promise<void> {
// If we already have a panel, show it.
// Otherwise, create a new panel.
if (ReactPanel.currentPanel) {
console.log('--- HAS CURRENT PANEL ---')
ReactPanel.currentPanel._panel.reveal(column)
if (this.panel && this.panel.webview) {
console.log('reveal')
this.panel.reveal(column)
} else {
ReactPanel.currentPanel = new ReactPanel(extensionPath, column)
console.log('make new panel')
this.panel = this.createWebviewPanel(column)

}
}

private constructor(extensionPath: string, column: vscode.ViewColumn) {
this._extensionPath = extensionPath

private createWebviewPanel(column: number): vscode.WebviewPanel {
const viewType = 'CodeRoad'
const title = 'CodeRoad'
const config = {
// Enable javascript in the webview
enableScripts: true,

// And restric the webview to only loading content from our extension's `media` directory.
localResourceRoots: [vscode.Uri.file(path.join(this._extensionPath, 'build'))],

localResourceRoots: [vscode.Uri.file(path.join(this.extensionPath, 'build'))],
// prevents destroying the window when it is in the background
retainContextWhenHidden: true,
}
return vscode.window.createWebviewPanel(viewType, title, column, config)
}

// Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(viewType, title, column, config)

// Set the webview's initial html content
this._panel.webview.html = this._getHtmlForWebview()

// Listen for when the panel is disposed
// This happens when the user closes the panel or when the panel is closed programatically
this._panel.onDidDispose(() => this.dispose(), null, this._disposables)

// Handle messages from the webview
this._panel.webview.onDidReceiveMessage(onReceive, null, this._disposables)
private getNonce(): string {
let text = ''
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length))
}
return text
}

public async postMessage(action: CR.Action): Promise<void> {
console.log('webview postMessage')
console.log(action)
// Send a message to the webview webview.
// You can send any JSON serializable data.
const success = await this._panel.webview.postMessage(action)
const success = await this.panel.webview.postMessage(action)
if (!success) {
throw new Error(`Message post failure: ${JSON.stringify(action)}`)
}
console.log('postMessage sent')
}

public dispose(): void {
ReactPanel.currentPanel = undefined

// Clean up our resources
this._panel.dispose()
this.panel.dispose()

while (this._disposables.length) {
const x = this._disposables.pop()
while (this.disposables.length) {
const x = this.disposables.pop()
if (x) {
x.dispose()
}
}
}

private _getHtmlForWebview(): string {
private getHtmlForWebview(): string {

// eslint-disable-next-line
const manifest = require(path.join(this._extensionPath, 'build', 'asset-manifest.json'))
const manifest = require(path.join(this.extensionPath, 'build', 'asset-manifest.json'))
const mainScript = manifest.files['main.js']
// grab first chunk
const chunk = Object.keys(manifest.files).filter(f => f.match(/^static\/js\/.+\.js$/))[0]
const chunkScript = manifest.files[chunk]
const mainStyle = manifest.files['main.css']

const scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', mainScript))
const scriptPathOnDisk = vscode.Uri.file(path.join(this.extensionPath, 'build', mainScript))
const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' })
const chunkPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', chunkScript))
const chunkPathOnDisk = vscode.Uri.file(path.join(this.extensionPath, 'build', chunkScript))
const chunkUri = chunkPathOnDisk.with({ scheme: 'vscode-resource' })
const stylePathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', mainStyle))
const stylePathOnDisk = vscode.Uri.file(path.join(this.extensionPath, 'build', mainStyle))
const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' })

// Use a nonce to whitelist which scripts can be run
const nonce = getNonce()
const nonce2 = getNonce()
const nonce3 = getNonce()
const nonce = this.getNonce()
const nonce2 = this.getNonce()
const nonce3 = this.getNonce()

const output = `<!DOCTYPE html>
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
Expand All @@ -121,7 +123,7 @@ class ReactPanel {
<link rel="manifest" href="./manifest.json" />
<link rel="stylesheet" type="text/css" href="${styleUri}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}' 'nonce-${nonce2}' 'nonce-${nonce3}'; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
<base href="${vscode.Uri.file(path.join(this._extensionPath, 'build')).with({ scheme: 'vscode-resource' })}/">
<base href="${vscode.Uri.file(path.join(this.extensionPath, 'build')).with({ scheme: 'vscode-resource' })}/">
<style></style>
</head>

Expand All @@ -133,9 +135,7 @@ class ReactPanel {
<script nonce="${nonce3}" src="${scriptUri}"></script>
</body>
</html>`
console.log(output)
return output
}
}

export default ReactPanel
export default ReactWebView
104 changes: 76 additions & 28 deletions src/editor/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,84 @@
import * as vscode from 'vscode'
import start from './start'
import ReactPanel from '../views/createWebview'

// import runTest from './runTest'
// import loadSolution from './loadSolution'
// import quit from './quit'
import { join } from 'path'
import { setStorage } from '../storage'
import ReactWebView from '../ReactWebView'
import * as CR from 'typings'

const COMMANDS = {
// TUTORIAL_SETUP: 'coderoad.tutorial_setup',
START: 'coderoad.start',
OPEN_WEBVIEW: 'coderoad.open_webview'
// RUN_TEST: 'coderoad.test_run',
// LOAD_SOLUTION: 'coderoad.solution_load',
// QUIT: 'coderoad.quit',
START: 'coderoad.start',
NEW_OR_CONTINUE: 'coderoad.new_or_continue',
OPEN_WEBVIEW: 'coderoad.open_webview',
SEND_STATE: 'coderoad.send_state',
SEND_DATA: 'coderoad.send_data',
RECEIVE_ACTION: 'coderoad.receive_action',
OPEN_FILE: 'coderoad.open_file',
RUN_TEST: 'coderoad.test_run',
}

interface CreateCommandProps {
context: vscode.ExtensionContext,
machine: CR.StateMachine,
storage: any,
git: any
}

// React panel webview
let webview: any;

export default (context: vscode.ExtensionContext): void => {
const commands = {
[COMMANDS.START]: async function startCommand(): Promise<void> {
return start(context)
export const createCommands = ({ context, machine, storage, git }: CreateCommandProps) => ({
// initialize
[COMMANDS.START]: () => {
// set local storage workspace
setStorage(context.workspaceState)

// activate machine
webview = new ReactWebView(context.extensionPath)
console.log('webview', webview.panel.webview.postMessage)
machine.activate()
},
[COMMANDS.OPEN_WEBVIEW]: () => {
ReactPanel.createOrShow(context.extensionPath);
[COMMANDS.NEW_OR_CONTINUE]: async () => {
// verify that the user has a tutorial & progress
// verify git is setup with a coderoad remote
const [tutorial, progress, hasGit, hasGitRemote] = await Promise.all([
storage.getTutorial(),
storage.getProgress(),
git.gitVersion(),
git.gitCheckRemoteExists(),
])
const canContinue = !!(tutorial && progress && hasGit && hasGitRemote)
console.log('canContinue', canContinue)
// if a tutorial exists, 'CONTINUE'
// otherwise start from 'NEW'
machine.send(canContinue ? 'CONTINUE' : 'NEW')
},
// [COMMANDS.RUN_TEST]: runTest,
// [COMMANDS.LOAD_SOLUTION]: loadSolution,
// [COMMANDS.QUIT]: () => quit(context.subscriptions),
}

for (const cmd in commands) {
const command: vscode.Disposable = vscode.commands.registerCommand(cmd, commands[cmd])
context.subscriptions.push(command)
}
}
// open React webview
[COMMANDS.OPEN_WEBVIEW]: (column: number = vscode.ViewColumn.One) => {
webview.createOrShow(column);
},
// open a file
[COMMANDS.OPEN_FILE]: async (relativeFilePath: string) => {
console.log(`OPEN_FILE ${JSON.stringify(relativeFilePath)}`)
try {
const workspaceRoot = vscode.workspace.rootPath
if (!workspaceRoot) {
throw new Error('No workspace root path')
}
const absoluteFilePath = join(workspaceRoot, relativeFilePath)
const doc = await vscode.workspace.openTextDocument(absoluteFilePath)
await vscode.window.showTextDocument(doc, vscode.ViewColumn.One)
} catch (error) {
console.log(`Failed to open file ${relativeFilePath}`, error)
}
},
// send messages to webview
[COMMANDS.SEND_STATE]: (payload: { data: any, state: any }) => {
webview.postMessage({ type: 'SET_STATE', payload })
},
[COMMANDS.SEND_DATA]: (payload: { data: any }) => {
webview.postMessage({ type: 'SET_DATA', payload })
},
[COMMANDS.RECEIVE_ACTION]: (action: string | CR.Action) => {
console.log('onReceiveAction', action)
machine.onReceive(action)
}
})
5 changes: 0 additions & 5 deletions src/editor/commands/quit.ts

This file was deleted.

45 changes: 0 additions & 45 deletions src/editor/commands/start.ts

This file was deleted.

Loading