diff --git a/toolbox/fdc3-explained/1.0/index.html b/toolbox/fdc3-explained/1.0/index.html new file mode 100644 index 000000000..d4cad56cf --- /dev/null +++ b/toolbox/fdc3-explained/1.0/index.html @@ -0,0 +1,90 @@ + + + + FDC3 Explained + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Back to main page.
FDC3 Provider:FDC3 Not Available
FDC3 Version: + +

App Directory:No Application Directory Connected
Available Apps:

Broadcast Context: + + + +

Get Context: + +
+ +
+ + + + diff --git a/toolbox/fdc3-explained/1.0/main.js b/toolbox/fdc3-explained/1.0/main.js new file mode 100644 index 000000000..080848018 --- /dev/null +++ b/toolbox/fdc3-explained/1.0/main.js @@ -0,0 +1,46 @@ +// enable application when FDC3 is available +document.addEventListener('DOMContentLoaded', () => { + fdc3Init(enablePage); +}); + +// check if FDC3 is available +function fdc3Init(callback) { + let fdc3Tries = 10 + + const onFDC3Ready = () => { + if (window.fdc3) { + callback.call(this); + } else { + if (fdc3Tries > 0) { + fdc3Tries--; + window.setTimeout(onFDC3Ready, 100); + } + } + } + + onFDC3Ready(); +} + +const providerDetails = document.getElementById("providerDetails") +const broadcastButton = document.getElementById("broadcastButton") +const broadcastText = document.getElementById("broadcastText") + +function enablePage() { + console.log('FDC3 is available'); + + if (window.FSBL) { + window.FSBL.getFSBLInfo().then(info => providerDetails.innerHTML = 'Available - Finsemble ' + info.FSBLVersion); + } else if (window.fin) { + providerDetails.innerHTML = 'Available - OpenFin ' + fin.desktop.getVersion(); + } else { + providerDetails.innerHTML = 'Available - Unknown'; + } + + broadcastButton.disabled = false; + broadcastText.disabled = false; +} + +function broadcastFDC3Context() { + var context = JSON.parse(broadcastText.value); + fdc3.broadcast(context); +} diff --git a/toolbox/fdc3-explained/1.0/styles.css b/toolbox/fdc3-explained/1.0/styles.css new file mode 100644 index 000000000..b4e8c33e6 --- /dev/null +++ b/toolbox/fdc3-explained/1.0/styles.css @@ -0,0 +1,25 @@ +textarea { + width: 400px; + height: 150px; +} + +select { + width: 150px; + height: 24px; +} + +.ctxInput { + width: 241px; + height: 24px; +} + +.urlInput { + width: 400px; + height: 24px; +} + +.header { + width: 200px; + height: 40px; + font-weight: 700; +} diff --git a/toolbox/fdc3-explained/1.1/index.html b/toolbox/fdc3-explained/1.1/index.html new file mode 100644 index 000000000..352dfe889 --- /dev/null +++ b/toolbox/fdc3-explained/1.1/index.html @@ -0,0 +1,156 @@ + + + + FDC3 Explained + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Back to main page.

FDC3 Supported Checking...
FDC3 Provider:None
FDC3 Version: + +

App Directory:Not supported yet!
Available Apps:

Available Channels: + System Channels: +
    + App Channels: +
      +
      Add App Channel:
      Join Channel:Channel: + +

      Broadcast Context:
      + + + +
      Context received:
      Context Type:

      Raise Intent:
      Intent:
      + + +
      +
      + + + diff --git a/toolbox/fdc3-explained/1.1/main.js b/toolbox/fdc3-explained/1.1/main.js new file mode 100644 index 000000000..947761008 --- /dev/null +++ b/toolbox/fdc3-explained/1.1/main.js @@ -0,0 +1,208 @@ +document.addEventListener('DOMContentLoaded', () => { + fdc3OnReady(main, displayFDC3Support); +}); + +function fdc3OnReady(success, error) { + let fdc3Tries = 10; + + const checkFDC3Ready = () => { + if (window.fdc3) { + success.call(this); + } else { + if (fdc3Tries > 0) { + fdc3Tries--; + window.setTimeout(checkFDC3Ready, 100); + } else { + error.call(this); + } + } + }; + checkFDC3Ready(); +} + +// use this to keep track of context listener - one per system channel +let contextListener = null; +let appChannels = []; + +function main() { + try { + console.log('FDC3 is ready and DOM has rendered'); + displayFDC3Support(); + getPlatform(); + populateHTML(); + setUpEventListeners(); + getContext(); + } catch (error) { + console.error(error); + } +} + +function displayFDC3Support() { + try { + const supportedElement = document.getElementById('fdc3-support'); + if (window.fdc3) { + supportedElement.innerHTML = 'Yes ✅'; + } else { + supportedElement.innerHTML = 'No ❌'; + } + } catch (error) { + console.error("Can't find FDC3 support", error); + } +} + +function getPlatform() { + const providerDetails = document.getElementById('providerDetails'); + + // TODO: add G42 and FDC3 Desktop Agent to vendors + if (window.FSBL) { + window.FSBL.getFSBLInfo().then(info => (providerDetails.innerHTML = 'Finsemble ' + info.FSBLVersion)); + } else if (window.fin) { + providerDetails.innerHTML = 'OpenFin ' + fin.desktop.getVersion(); + } else { + providerDetails.innerHTML = 'Unknown'; + } +} + +async function populateHTML() { + try { + //populate available channels list with system channels + const channelList = document.getElementById('system-channel-list'); + + const populateChannelsList = id => { + let node = document.createElement('li'); + let textNode = document.createTextNode(id); + node.appendChild(textNode); + channelList.appendChild(node); + }; + + const systemChannels = await fdc3.getSystemChannels(); + + // for all of the system channels populate dropdowns & lists + systemChannels.forEach(({ displayMetadata, id, type }) => { + populateChannelsList(id); + populateChannelsDropDown(id); + }); + + // as FDC3 is supported we can enable the buttons again except those that are not yet supported features + document.querySelectorAll('button').forEach(button => { + if (!button.className.includes('not-supported')) { + button.disabled = false; + } + }); + } catch (error) { + console.error('unable to populate the html for the page ', error); + } +} + +function setUpEventListeners() { + document.getElementById('add-app-channel__btn').addEventListener('click', addAppChannel); + + document.getElementById('join-channel__btn').addEventListener('click', joinChannel); + + document.getElementById('leave-channel__btn').addEventListener('click', () => { fdc3.leaveCurrentChannel(); }); + + document.getElementById('broadcast__btn').addEventListener('click', broadcastFDC3Context); + + document.getElementById('raise-intent__btn').addEventListener('click', raiseIntent); + + document.getElementById('get_context__btn').addEventListener('click', event => { + let contextType = document.getElementById('context-type').value; + getContext(contextType); + }); +} + +/** + *Populate the channel dropdown elements + */ +function populateChannelsDropDown(newOptionText) { + try { + let dropdownElement = document.querySelector('.fdc3-channels'); + + if (newOptionText) { + dropdownElement.add(new Option(newOptionText)); + } else { + throw new Error('No option provided'); + } + } catch (error) { + console.error('could not add a new channel to the channel dropdown list', error); + } +} + +function joinChannel() { + try { + let dropdownElement = document.getElementById('join-channel'); + let channelName = dropdownElement.options[dropdownElement.selectedIndex].text; + fdc3.joinChannel(channelName); + } catch (error) { + console.error("Can't join channel", error); + } +} + +async function broadcastFDC3Context() { + try { + let contextData = document.getElementById('txtBroadcastData').value; + fdc3.broadcast(JSON.parse(contextData)); + } catch (error) { + console.error('could not broadcast', error); + } +} + +async function getContext(contextType) { + try { + let contextResultBox = document.getElementById('context-result'); + if (contextListener) contextListener.unsubscribe(); + + // if context type is passed in then only listen on that specific context + if (contextType) { + contextListener = fdc3.addContextListener( + contextType, + context => (contextResultBox.value = JSON.stringify(context, null, 2)) + ); + } else { + contextListener = fdc3.addContextListener(context => (contextResultBox.value = JSON.stringify(context, null, 2))); + } + } catch (error) { + console.error('Unable to add a context listener', error); + } +} + +async function addAppChannel() { + try { + let appChannelName = document.getElementById('app-channel').value; + + if (!appChannelName) throw new Error('no channel name set'); + + let appChannelExists = appChannels.find(appChannel => appChannel.id === appChannelName); + + if (!appChannelExists) { + let newAppChannel = await fdc3.getOrCreateChannel(appChannelName); + appChannels.push(newAppChannel); + + // add to the list of available app channels + let node = document.createElement('li'); + let textNode = document.createTextNode(appChannelName); + node.appendChild(textNode); + document.getElementById('app-channel-list').appendChild(node); + + //populate the channel list dropdown with new appChannel + populateChannelsDropDown(newAppChannel.id); + } else { + throw new Error('app channel already exists'); + } + } catch (error) { + console.error('could not add an app channel', error); + } +} + +async function raiseIntent() { + try { + // get the channel + let intent = document.getElementById('intent').value; + let context = JSON.parse(document.getElementById('intent-context').value); + + // TODO: add the target param + await fdc3.raiseIntent(intent, context); + } catch (err) { + console.error('intent did not resolve', err); + } +} diff --git a/toolbox/fdc3-explained/1.1/styles.css b/toolbox/fdc3-explained/1.1/styles.css new file mode 100644 index 000000000..82dd27ac4 --- /dev/null +++ b/toolbox/fdc3-explained/1.1/styles.css @@ -0,0 +1,49 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap'); + +body { + font-family: "Noto Sans JP",sans-serif; + margin:0; + padding:0; + background-color: white; +} + +.main { + margin: 10px 0; + padding: 0; + display: flex; + place-content: center; +} +table { + border:0; +} +tr{ + border:0; +} +td{ + border:0; +} +textarea { + width: 400px; + height: 150px; +} + +select { + width: 238px; + height: 24px; +} + +.ctxInput { + width: 241px; + height: 24px; +} + +.urlInput { + width: 400px; + height: 24px; +} + +.header { + width: 200px; + height: 40px; + font-weight: 700; +} diff --git a/toolbox/fdc3-explained/1.2/index.html b/toolbox/fdc3-explained/1.2/index.html new file mode 100644 index 000000000..3846a8dd8 --- /dev/null +++ b/toolbox/fdc3-explained/1.2/index.html @@ -0,0 +1,157 @@ + + + + + FDC3 Explained + + + + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      FDC3 Supported Checking...
      FDC3 Provider:None
      FDC3 Version: + +

      App Directory:Not supported yet!
      Available Apps:

      Available Channels: + System Channels: +
        + App Channels: +
          +
          Add App Channel:
          Join Channel:Channel: + +

          Broadcast Context:
          + +
          Context received:
          Context Type:
          +
          + Context result will appear here. +
          +
          +
          +
          Raise Intent:
          Intent:
          + +
          +
          + + + diff --git a/toolbox/fdc3-explained/1.2/main.js b/toolbox/fdc3-explained/1.2/main.js new file mode 100644 index 000000000..427624933 --- /dev/null +++ b/toolbox/fdc3-explained/1.2/main.js @@ -0,0 +1,208 @@ +// check for FDC3 support +function fdc3OnReady(success, error) { + window.setTimeout(error, 1000); + if (window.fdc3) { + success(); + } else { + window.addEventListener('fdc3Ready', success); + } +} + +// Wait for the document to load +function documentLoaded(cb) { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', cb); + } else { + cb(); + } +} + +// use this to keep track of context listener - one per system channel +let contextListener = null; +let appChannels = []; + +// document and FDC3 have loaded start the main function +documentLoaded(() => { + fdc3OnReady(main, displayFDC3Support); +}); + +function main() { + try { + console.log('FDC3 is ready and DOM has rendered'); + displayFDC3Support(); + getPlatform(); + populateHTML(); + setUpEventListeners(); + getContext(); + } catch (error) { + console.error(error); + } +} + +function displayFDC3Support() { + try { + const supportedElement = document.getElementById('fdc3-support'); + if (window.fdc3) { + supportedElement.innerHTML = 'Yes ✅'; + } else { + supportedElement.innerHTML = 'No ❌'; + } + } catch (error) { + console.error("Can't find FDC3 support", error); + } +} + +function getPlatform() { + const providerDetails = document.getElementById('providerDetails'); + const fdc3Info = window.fdc3.getInfo(); + console.log('FDC3 info', fdc3Info); + + providerDetails.innerHTML = `${fdc3Info.provider} ${fdc3Info.providerVersion}`; +} + +async function populateHTML() { + try { + //populate available channels list with system channels + let channelList = document.getElementById('system-channel-list'); + + const populateChannelsList = id => { + let node = document.createElement('li'); + let textNode = document.createTextNode(id); + node.appendChild(textNode); + channelList.appendChild(node); + }; + + const systemChannels = await fdc3.getSystemChannels(); + + // for all of the system channels populate dropdowns & lists + systemChannels.forEach(({ displayMetadata, id, type }) => { + //use the id field as this is what is needed to join the channel + populateChannelsList(id); + populateChannelsDropDown(id); + }); + + // as FDC3 is supported we can enable the buttons again except those that are not yet supported features + document.querySelectorAll('button').forEach(button => { + if (!button.className.includes('not-supported')) { + button.disabled = false; + } + }); + } catch (error) { + console.error('unable to populate the html for the page ', error); + } +} + +function setUpEventListeners() { + document.getElementById('add-app-channel__btn').addEventListener('click', addAppChannel); + + document.getElementById('join-channel__btn').addEventListener('click', joinChannel); + + document.getElementById('leave-channel__btn').addEventListener('click', () => { fdc3.leaveCurrentChannel(); }); + + document.getElementById('broadcast__btn').addEventListener('click', broadcastFDC3Context); + + document.getElementById('raise-intent__btn').addEventListener('click', raiseIntent); + + document.getElementById('get_context__btn').addEventListener('click', event => { + let contextType = document.getElementById('context-type').value; + getContext(contextType); + }); +} + +/** + *Populate the channel dropdown elements + */ +function populateChannelsDropDown(newOptionText) { + try { + let dropdownElement = document.querySelector('.fdc3-channels'); + + if (newOptionText) { + dropdownElement.add(new Option(newOptionText)); + } else { + throw new Error('No option provided'); + } + } catch (error) { + console.error('could not add a new channel to the channel dropdown list', error); + } +} + +function joinChannel() { + try { + let dropdownElement = document.getElementById('join-channel'); + let channelName = dropdownElement.options[dropdownElement.selectedIndex].text; + fdc3.joinChannel(channelName); + } catch (error) { + console.error("Can't join channel", error); + } +} + +async function broadcastFDC3Context() { + try { + let contextData = document.getElementById('txtBroadcastData').value; + fdc3.broadcast(JSON.parse(contextData)); + } catch (error) { + console.error('could not broadcast', error); + } +} + +async function getContext(contextType) { + try { + let contextResultBox = document.getElementById('context-result'); + if (contextListener) contextListener.unsubscribe(); + + // if context type is passed in then only listen on that specific context + if (contextType) { + contextListener = fdc3.addContextListener( + contextType, + context => (contextResultBox.innerHTML = "
          " + JSON.stringify(context, null, 2)) + "
          " + ); + } else { + contextListener = fdc3.addContextListener( + context => (contextResultBox.innerHTML= "
          " + JSON.stringify(context, null, 2)) + "
          " + ); + } + } catch (error) { + console.error('Unable to add a context listener', error); + } +} + +async function addAppChannel() { + try { + let appChannelName = document.getElementById('app-channel').value; + + if (!appChannelName) throw new Error('no channel name set'); + + let appChannelExists = appChannels.find(appChannel => appChannel.id === appChannelName); + + if (!appChannelExists) { + let newAppChannel = await fdc3.getOrCreateChannel(appChannelName); + appChannels.push(newAppChannel); + + // add to the list of available app channels + let node = document.createElement('li'); + let textNode = document.createTextNode(appChannelName); + node.appendChild(textNode); + document.getElementById('app-channel-list').appendChild(node); + + //populate the channel list dropdown with new appChannel + populateChannelsDropDown(newAppChannel.id); + } else { + throw new Error('app channel already exists'); + } + } catch (error) { + console.error('could not add an app channel', error); + } +} + +async function raiseIntent() { + try { + // get the channel + let intent = document.getElementById('intent').value; + let context = JSON.parse(document.getElementById('intent-context').value); + + // TODO: add the target param + await fdc3.raiseIntent(intent, context); + } catch (err) { + console.error('intent did not resolve', err); + } +} diff --git a/toolbox/fdc3-explained/1.2/styles.css b/toolbox/fdc3-explained/1.2/styles.css new file mode 100644 index 000000000..8cab74c03 --- /dev/null +++ b/toolbox/fdc3-explained/1.2/styles.css @@ -0,0 +1,116 @@ +@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap"); + +.body { + font-family: "Noto Sans JP", sans-serif; + margin: 0; + padding: 0; + background-color: #284b63; + color: white; +} + +.main { + margin: 10px 0; + padding: 0; + display: flex; + flex-wrap: wrap; + place-content: center; + flex-direction: column; +} +#logo { + height: 200px; + margin-left: 20px; +} +button { + min-width: 45px; + height: 22px; + background: rgb(210 236 255); + border: 2px solid rgb(0 0 0 / 0%); + border-radius: 5px; + box-shadow: #03a9f433 2px 3px 0px 0px; + cursor: pointer; +} +button:active { + outline: transparent; + box-shadow: inset #3c3c3c70 2px 3px 3px 0px, #03a9f433 1px 2px 0px 0px; + transform: translateY(2px); + transition: ease-out 0.1s; +} +button:focus { + outline: transparent; +} +input { + background: #e8f7ff; + box-shadow: inset -2px -2px 2px 0px rgb(255 255 255 / 12%), + inset 2px 3px 6px 2px rgb(0 0 0 / 19%); + border-radius: 6px; + border: 2px solid transparent; +} +input:focus { + transition-property: border; + transition-duration: 0.8s; + border: 2px solid #368cc7; + outline: 0; +} +textarea { + background: #e8f7ff; + box-shadow: inset -3px -5px 5px 1px rgb(255 255 255 / 12%), + inset 2px 3px 6px 2px rgb(0 0 0 / 19%); + border-radius: 6px; + border: 2px solid transparent; +} +textarea:focus { + transition-property: border; + transition-duration: 0.8s; + border: 2px solid #368cc7; + outline: 0; +} +table { + border: 0; + margin: 0 auto; + max-width: 1000px; + display: flex; +} +tr { + border: 0; +} +td { + border: 0; +} +textarea { + width: 400px; + height: 150px; +} + +select { + width: 238px; + height: 24px; + width: 238px; + height: 24px; + border-radius: 6px; + background: #e8f7ff; + border: 2px solid rgb(0 0 0 / 0%); + box-shadow: #03a9f433 2px 3px 0px 0px; +} + +.ctxInput { + width: 241px; + height: 24px; +} + +.urlInput { + width: 400px; + height: 24px; +} + +.header { + width: 200px; + height: 40px; + font-weight: 700; +} + +#context-result { + border: 1px solid white; + padding: 17px; + color: #9fffa2; + font-family: monospace; +} diff --git a/toolbox/fdc3-explained/README.md b/toolbox/fdc3-explained/README.md new file mode 100644 index 000000000..b23c2df39 --- /dev/null +++ b/toolbox/fdc3-explained/README.md @@ -0,0 +1,21 @@ +# FDC3 Explained Logo + +FDC3 Explained was originally created by Johan Sandersson, an original maintainer of FDC3, and contributed to the project. It shows how easy it is to create very simple HTML and JavaScript that can use the FDC3 APIs to facilitate interoperability. + +It is also a useful tool for detecing whether FDC3 is enabled, by running the website inside an FDC3-supporting desktop agent, and for performing basic FDC3 operations to help test interoperability workflows. + +## Website + +FDC3 Explained can be accessed at the following URL: https://fdc3.finos.org/toolbox/fdc3-explained. + +Note that it will only detect and allow use of FDC3 when running in the context of an FDC3 desktop agent. + +## Build + +The NPM script `copy-explained` in the `website` folder copies the relevant files to the FDC3 website build for deployment. + +It is automatically executed during deployments to https://fdc3.finos.org. + +## Note + +FDC3 Explained does not aim to be an exhaustive reference implementation for each version of the FDC3 Standard, it is merely a convenient testing utility. diff --git a/toolbox/fdc3-explained/index.html b/toolbox/fdc3-explained/index.html new file mode 100644 index 000000000..d8c7ebdae --- /dev/null +++ b/toolbox/fdc3-explained/index.html @@ -0,0 +1,82 @@ + + + + FDC3 Explained + + + + + + + + +
          + + + + + + + + + + + + +
          + FDC3 Explained is a community project and reference implementation + of various FDC3 functionality. The key goal is to provide a tool for + application and container developers to verify their implementations + and act as basic but fully functional participant when testing and + demonstrating desktop interoperability. +
          +
          + + diff --git a/toolbox/fdc3-explained/logo.svg b/toolbox/fdc3-explained/logo.svg new file mode 100644 index 000000000..0cf0d70af --- /dev/null +++ b/toolbox/fdc3-explained/logo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/website/package.json b/website/package.json index 70764f564..00a9bd6f1 100644 --- a/website/package.json +++ b/website/package.json @@ -2,7 +2,7 @@ "scripts": { "examples": "docusaurus-examples", "start": "docusaurus-start", - "prebuild": "yarn run copy-appd && yarn run copy-schemas && yarn run copy-workbench", + "prebuild": "yarn run copy-appd && yarn run copy-schemas && yarn run copy-workbench && yarn run copy-explained", "build": "docusaurus-build", "prepublish-gh-pages": "yarn run copy-appd && yarn run copy-schemas && yarn run copy-workbench", "publish-gh-pages": "docusaurus-publish", @@ -10,6 +10,7 @@ "copy-appd": "cpy ../src/app-directory/specification/appd.yaml static/schemas/next --rename app-directory.yaml", "build-workbench": "cd ../toolbox/fdc3-workbench && yarn install && yarn build", "copy-workbench": "yarn del static/toolbox/fdc3-workbench && yarn build-workbench && cd ../toolbox/fdc3-workbench/build && cpy ** ../../../website/static/toolbox/fdc3-workbench --parents", + "copy-explained": "yarn del static/toolbox/fdc3-explained && cpy ./fdc3-explained ../website/static/toolbox --parents --cwd=../toolbox", "version": "yarn run version:docs && yarn run version:schemas && yarn run version:appd", "version:docs": "docusaurus-version ${VERSION}", "version:schemas": "cpy static/schemas/next/*.schema.json static/schemas/${VERSION} && replace-in-files --string=/schemas/next --replacement=/schemas/${VERSION} static/schemas/${VERSION}/*.schema.json",