-
Notifications
You must be signed in to change notification settings - Fork 717
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #695 from blurymind/master
Add jsfx sound effects editor
- Loading branch information
Showing
13 changed files
with
538 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
const electron = require('electron'); | ||
const fs = require('fs'); | ||
const remote = electron.remote; | ||
const { | ||
dialog | ||
} = remote; | ||
|
||
export const createPathEditorHeader = ({ | ||
parentElement, | ||
editorContentDocument, | ||
onSaveToGd, | ||
onCancelChanges, | ||
projectPath, | ||
initialResourcePath, | ||
extension, | ||
headerStyle, | ||
}) => { | ||
if (fs.lstatSync(initialResourcePath).isDirectory()) { | ||
initialResourcePath = initialResourcePath + '/NewFile' + extension; | ||
} | ||
|
||
const headerObject = { | ||
state: { | ||
folderPath: initialResourcePath.substring( | ||
0, | ||
initialResourcePath.lastIndexOf('/') | ||
), | ||
name: initialResourcePath.substring( | ||
initialResourcePath.lastIndexOf('/') + 1, | ||
initialResourcePath.lastIndexOf('.') | ||
), | ||
extension: initialResourcePath.substring( | ||
initialResourcePath.lastIndexOf('.'), | ||
initialResourcePath.length | ||
), | ||
projectBasePath: projectPath, | ||
}, | ||
}; | ||
|
||
const state = headerObject.state; | ||
// create the dom elements of the ui | ||
headerObject.saveFolderLabel = editorContentDocument.createElement('label'); | ||
headerObject.saveFolderLabel.style = headerStyle.saveFolderLabel; | ||
headerObject.saveFolderLabel.textContent = state.folderPath; | ||
parentElement.appendChild(headerObject.saveFolderLabel); | ||
|
||
headerObject.nameInput = editorContentDocument.createElement('input'); | ||
headerObject.nameInput.type = 'text'; | ||
headerObject.nameInput.style = headerStyle.nameInput; | ||
headerObject.nameInput.value = state.name; | ||
parentElement.appendChild(headerObject.nameInput); | ||
|
||
headerObject.fileExistsLabel = editorContentDocument.createElement('label'); | ||
headerObject.fileExistsLabel.style = headerStyle.fileExistsLabel; | ||
parentElement.appendChild(headerObject.fileExistsLabel); | ||
|
||
headerObject.saveButton = editorContentDocument.createElement('button'); | ||
headerObject.saveButton.textContent = 'Save'; | ||
headerObject.saveButton.style = headerStyle.saveButton; | ||
parentElement.appendChild(headerObject.saveButton); | ||
|
||
headerObject.cancelButton = editorContentDocument.createElement('button'); | ||
headerObject.cancelButton.textContent = 'Cancel'; | ||
headerObject.cancelButton.style = headerStyle.cancelButton; | ||
parentElement.appendChild(headerObject.cancelButton); | ||
|
||
headerObject.setFolderButton = editorContentDocument.createElement('button'); | ||
headerObject.setFolderButton.textContent = 'Set Folder'; | ||
headerObject.setFolderButton.style = headerStyle.setFolderButton; | ||
parentElement.appendChild(headerObject.setFolderButton); | ||
|
||
// From here on we hook the dom with the imported or local methods via event listeners | ||
headerObject.nameInput.addEventListener('input', () => { | ||
render(headerObject); | ||
}); | ||
headerObject.saveButton.addEventListener('click', () => { | ||
onSaveToGd(headerObject); | ||
}); | ||
headerObject.cancelButton.addEventListener('click', onCancelChanges); | ||
headerObject.saveFolderLabel.addEventListener('click', () => { | ||
selectBaseFolderPath(headerObject); | ||
}); | ||
headerObject.setFolderButton.addEventListener('click', () => { | ||
selectBaseFolderPath(headerObject); | ||
}); | ||
render(headerObject); | ||
}; | ||
|
||
const render = headerObject => { | ||
headerObject.nameInput.value = headerObject.nameInput.value.replace( | ||
/[^a-zA-Z0-9_-]/g, | ||
'' | ||
); // Don't allow the user to enter any characters that would lead to an invalid path | ||
const state = headerObject.state; | ||
state.name = headerObject.nameInput.value; | ||
state.baseExportPath = state.folderPath + '/' + state.name; | ||
state.fullPath = state.folderPath + '/' + state.name + state.extension; | ||
headerObject.saveFolderLabel.textContent = state.folderPath + '/'; | ||
headerObject.saveFolderLabel.title = | ||
'Click to change path: \n' + state.folderPath; | ||
|
||
headerObject.nameInput.style.width = | ||
(headerObject.nameInput.value.length + 1) * 10 + 'px'; | ||
// check if it will overwrite a file and notify the user in a subtle, but obvious way | ||
if (fs.existsSync(state.fullPath)) { | ||
headerObject.fileExistsLabel.style.color = 'red'; | ||
headerObject.fileExistsLabel.textContent = | ||
state.extension + ' (Overwrite)'; | ||
} else { | ||
headerObject.fileExistsLabel.style.color = 'grey'; | ||
headerObject.fileExistsLabel.textContent = state.extension + ' (New)'; | ||
} | ||
}; | ||
|
||
const selectBaseFolderPath = headerObject => { | ||
const state = headerObject.state; | ||
if (!state.projectBasePath) { | ||
state.projectBasePath = state.folderPath; | ||
} | ||
const selectedDir = dialog.showOpenDialog(remote.getCurrentWindow(), { | ||
properties: ['openDirectory'], | ||
defaultPath: state.projectBasePath, | ||
}); | ||
if (!selectedDir) { | ||
return; | ||
} | ||
if (!selectedDir.toString().startsWith(state.projectBasePath)) { | ||
alert( | ||
'Please select a folder inside your project path!\n' + | ||
state.projectBasePath + | ||
'\n\nSelected:\n' + | ||
selectedDir | ||
); | ||
return; | ||
} | ||
state.folderPath = selectedDir; | ||
render(headerObject); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<html> | ||
|
||
<head> | ||
<title>GDevelop Jsfx Editor</title> | ||
<link rel="stylesheet" type="text/css" href="jsfx-style.css"> | ||
</head> | ||
|
||
<body> | ||
<div id='path-editor-header'></div> | ||
<iframe id='jsfx-frame' src="loov-jsfx/index.html"></iframe> | ||
<script type="module" src="jsfx-main.js"></script> | ||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { | ||
createPathEditorHeader | ||
} from '../Utils/pathEditor.js'; | ||
|
||
const electron = require('electron'); | ||
const ipcRenderer = electron.ipcRenderer; | ||
const fs = require('fs'); | ||
const remote = electron.remote; | ||
|
||
let editorContentDocument, | ||
jsfx = null; | ||
|
||
const loadMetaData = metaData => { | ||
jsfx.CurrentParams = metaData; | ||
jsfx.UpdateCurrentView(); | ||
jsfx.PlayCurrent(); | ||
}; | ||
|
||
const closeWindow = () => { | ||
remote.getCurrentWindow().close(); | ||
}; | ||
|
||
const saveSoundEffect = pathEditor => { | ||
jsfx.UpdateDownloadLink(); //Update base64 data | ||
const rawData = editorContentDocument | ||
.getElementById('download') | ||
.href.replace(/^data:audio\/wav;base64,/, ''); | ||
fs.writeFile(pathEditor.state.fullPath, rawData, 'base64', err => { | ||
ipcRenderer.send( | ||
'jsfx-changes-saved', | ||
pathEditor.state.fullPath, | ||
jsfx.CurrentParams | ||
); | ||
closeWindow(); | ||
}); | ||
}; | ||
|
||
// we need to first declare when the window is ready to be initiated | ||
document.addEventListener('DOMContentLoaded', function () { | ||
ipcRenderer.send('jsfx-ready'); | ||
}); | ||
// then trigger bellow from main, this ensures the dom is loaded | ||
ipcRenderer.on('jsfx-open', (event, receivedOptions) => { | ||
const editorFrameEl = document.getElementById('jsfx-frame'); | ||
jsfx = editorFrameEl.contentWindow; | ||
editorContentDocument = editorFrameEl.contentDocument; | ||
const presetsPanel = editorContentDocument.getElementById('presets'); | ||
|
||
// Load metadata, if it exists | ||
if ('jsfx' in receivedOptions.metadata) { | ||
loadMetaData(receivedOptions.metadata.jsfx); | ||
} else { | ||
// If not, simulate a click on the 'Lucky' button to create a random sound effect | ||
const generateRandomSoundEffectButton = presetsPanel.childNodes[11]; | ||
generateRandomSoundEffectButton.click(); | ||
} | ||
// load a custom header | ||
const pathEditorHeaderDiv = document.getElementById('path-editor-header'); | ||
const headerStyle = { | ||
saveFolderLabel: 'height:27px;color:SlateGrey;float: left;margin-left: 2px;margin-top: 10px; font-size:15px;', | ||
nameInput: 'font-family:"Courier New";height:27px;width:90px;color:SlateGrey;float:left;margin-left: 2px;padding:4px;margin-top: 4px;font-size:15px;border: 2px solid #e5cd50;border-radius: 3px; ', | ||
fileExistsLabel: 'height:27px;color:blue;float: left;margin-left: 2px;margin-top: 10px; font-size:15px;', | ||
saveButton: 'height:27px;float:right;margin-left:2px;margin-right:4px;border: 2px solid DeepSkyBlue;border-radius: 1px;margin-top: 5px;background-color:white;', | ||
cancelButton: 'height:27px;float:right;margin-right:2px;border: 2px solid DeepSkyBlue;border-radius: 1px;margin-top: 5px;background-color:white;', | ||
setFolderButton: 'height:27px;float:right;margin-left:2px;margin-right:4px;border: 2px solid DeepSkyBlue;border-radius: 1px;margin-top: 5px;background-color:white;', | ||
}; | ||
createPathEditorHeader({ | ||
parentElement: pathEditorHeaderDiv, | ||
editorContentDocument: document, | ||
onSaveToGd: saveSoundEffect, | ||
onCancelChanges: closeWindow, | ||
projectPath: receivedOptions.projectPath, | ||
initialResourcePath: receivedOptions.resourcePath, | ||
extension: '.wav', | ||
headerStyle, | ||
}); | ||
|
||
// alter the interface of the external editor | ||
editorContentDocument.getElementById('jsfx').firstChild.style = 'float:top'; | ||
const defaultTitle = editorContentDocument.getElementsByClassName('title')[0] | ||
.firstChild; | ||
defaultTitle.remove(); | ||
|
||
presetsPanel.className = 'description'; | ||
presetsPanel.style = 'float:left;'; | ||
|
||
const generatorsTitle = editorContentDocument.getElementsByClassName( | ||
'panel-title' | ||
)[1].firstChild; | ||
generatorsTitle.data = 'Create a Random Sound Effect:'; | ||
|
||
const description = editorContentDocument.getElementsByClassName( | ||
'description' | ||
)[0]; | ||
description.remove(); | ||
|
||
const libraryPanel = editorContentDocument.getElementsByClassName( | ||
'panel wide' | ||
)[0]; | ||
libraryPanel.style = 'visibility:hidden;height:0px;width:0px'; | ||
|
||
const defaultButtons = editorContentDocument.getElementById('control'); | ||
defaultButtons.style = 'visibility:hidden;height:0px;width:0px'; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
* Additional styles for the embedded Jsfx editor | ||
*/ | ||
|
||
html, | ||
body { | ||
font-family: Helvetica; | ||
margin: 0; | ||
overflow-y: hidden; | ||
background-color: white; | ||
} | ||
|
||
#jsfx-frame { | ||
width: 100%; | ||
height: 100%; | ||
border: none; | ||
overflow-y: hidden; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
var shell = require('shelljs'); | ||
|
||
var source = '../node_modules/loov-jsfx'; | ||
|
||
var success = true; | ||
success &= shell.mkdir('-p', '../public/External/jsfx'); | ||
success &= shell.cp( | ||
'-Rf', | ||
source, | ||
'../public/External/jsfx' | ||
); | ||
if (success) { | ||
shell.echo( | ||
`❌ Error(s) occurred while copying Jsfx Editor sources from node_modules/loov-jsfx` | ||
); | ||
} else { | ||
shell.echo( | ||
'✅ Sources of Jsfx Editor properly copied in public folder' | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import optionalRequire from '../Utils/OptionalRequire.js'; | ||
import { type ExternalEditorOpenOptions } from './ResourceExternalEditor.flow'; | ||
|
||
const electron = optionalRequire('electron'); | ||
const path = optionalRequire('path'); | ||
const ipcRenderer = electron ? electron.ipcRenderer : null; | ||
const gd = global.gd; | ||
|
||
/** | ||
* Open JSFX to create wav resources. | ||
*/ | ||
export const openJsfx = ({ | ||
project, | ||
resourcesLoader, | ||
resourceNames, | ||
onChangesSaved, | ||
resourcePath, | ||
extraOptions, | ||
}: ExternalEditorOpenOptions) => { | ||
if (!electron || !ipcRenderer) return; | ||
const projectPath = path.dirname(project.getProjectFile()); | ||
|
||
let initialResourcePath = ''; | ||
initialResourcePath = resourcesLoader.getFullUrl(project, resourceNames[0]); | ||
initialResourcePath = initialResourcePath.substring( | ||
7, | ||
initialResourcePath.lastIndexOf('?cache=') | ||
); | ||
|
||
const jsfxData = { | ||
resourcePath: initialResourcePath, | ||
metadata: extraOptions.initialResourceMetadata, | ||
projectPath, | ||
}; | ||
|
||
ipcRenderer.removeAllListeners('jsfx-changes-saved'); | ||
ipcRenderer.on('jsfx-changes-saved', (event, newFilePath, fileMetadata) => { | ||
const resourcesManager = project.getResourcesManager(); | ||
const resourceName = path.relative(projectPath, newFilePath); //Still needed for onChangesSaved() | ||
const audioResource = new gd.AudioResource(); | ||
audioResource.setFile(resourceName); | ||
audioResource.setName(resourceName); | ||
resourcesManager.addResource(audioResource); | ||
audioResource.delete(); | ||
const newMetadata = { | ||
jsfx: fileMetadata, | ||
}; | ||
|
||
onChangesSaved([{ metadata: newMetadata }], resourceName); | ||
}); | ||
|
||
ipcRenderer.send('jsfx-create-wav', jsfxData); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.