diff --git a/docusaurus.config.js b/docusaurus.config.js
index 5983d62..3b29d05 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -40,6 +40,11 @@ module.exports = {
position: 'left',
label: 'Examples'
},
+ {
+ to:'/recommendation',
+ label: 'Recommendation engine',
+ position: 'left',
+ },
// {to: '/blog', label: 'Blog', position: 'left'},
{
href: 'https://github.com/crossplatform-dev/crossplatform.dev',
diff --git a/src/components/RecommendationWizard.js b/src/components/RecommendationWizard.js
new file mode 100644
index 0000000..2a2e415
--- /dev/null
+++ b/src/components/RecommendationWizard.js
@@ -0,0 +1,208 @@
+import React, { useState } from 'react';
+import styles from './RecommendationWizard.module.css';
+import { questions } from '../data/questions.js';
+import { technologies } from '../data/technologies.js';
+
+const State = {
+ Waiting: 'waiting',
+ Questioning: 'questioning',
+ Ended: 'ended',
+};
+
+/**
+ * Based on the user's answers returns a list of technologies
+ * to look at in order of priority.
+ */
+const getRecommendations = (selectedTags) => {
+ const scoredTechnologies = [];
+ for (const technology of technologies) {
+ let score = 0;
+ let add = true;
+
+ for (const tag of selectedTags) {
+ const [feature, deal] = tag.split('-');
+ const weight = technology.categories[feature];
+ if ((deal === 'deal' && typeof weight === 'undefined') || weight === 0) {
+ // A 0 score on a category is a deal breaker
+ console.log(
+ `${technology.name} removed because of ${feature} is missing and it's a deal breaker`
+ );
+ add = false;
+ break;
+ }
+ score += weight || 0;
+ }
+
+ if (add) {
+ scoredTechnologies.push({
+ name: technology.name,
+ normalizedName: technology.normalizedName,
+ score,
+ });
+ }
+ }
+
+ const sortedTechnologies = scoredTechnologies.sort(
+ (technologyA, technologyB) => {
+ if (technologyA.score < technologyB.score) {
+ return 1;
+ }
+ if (technologyA.score == technologyB.score) {
+ return 0;
+ }
+
+ if (technologyA.score > technologyB.score) {
+ return -1;
+ }
+ }
+ );
+ return sortedTechnologies;
+};
+
+const FinalRecommendation = ({ restart, selections }) => {
+ const technologies = getRecommendations(selections);
+ if (technologies.length === 0) {
+ return (
+
+
+ We could not find any technology that checks all your criteria. Please
+ try again changing some of the values (like the targetted platforms).
+
+
+
+ );
+ }
+ return (
+
+
+ Based on your answers the technologies we think you should investigate
+ are:
+
+
+
+
+
+ Doesn't seem right? Open an{' '}
+
+ issue
+ {' '}
+ with more details!
+
+
+ );
+};
+
+/**
+ *
+ * @param {QuestioningProps} param0
+ * @returns
+ */
+const Questioning = ({ questions, done }) => {
+ const [question, setQuestion] = useState(questions[0]);
+ const [remainingQuestions, setRemainingQuestions] = useState(
+ questions.slice(1)
+ );
+ const [selectedTags, setTags] = useState([]);
+
+ /**
+ * Handles the selection changes of inputs in the form to make
+ * sure their state is updated in the React side.
+ */
+ const handleChange = (e) => {
+ const { checked, value } = e.target;
+ if (value === 'none') {
+ return;
+ }
+ const indexOf = selectedTags.indexOf(value);
+
+ if (checked) {
+ if (indexOf === -1) {
+ setTags([...selectedTags, value]);
+ }
+ } else if (indexOf !== -1) {
+ selectedTags.splice(indexOf, 1);
+ setTags([...selectedTags]);
+ }
+ };
+
+ /**
+ * Updates the user's selection for the current question
+ * and moves to the next one or the final step.
+ */
+ const handleSubmit = (evt) => {
+ evt.preventDefault();
+
+ if (remainingQuestions.length > 0) {
+ setQuestion(remainingQuestions[0]);
+ setRemainingQuestions(remainingQuestions.slice(1));
+ } else {
+ done(selectedTags);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default function RecommendationWizard() {
+ const [status, setState] = useState(State.Questioning);
+ const [selections, setSelections] = useState([]);
+
+ const done = (choices) => {
+ setSelections(choices);
+ setState(State.Ended);
+ };
+
+ const restart = () => {
+ setState(State.Questioning);
+ };
+
+ let section;
+ if (status === State.Waiting) {
+ section = ;
+ } else if (status === State.Questioning) {
+ section = ;
+ } else if (status === State.Ended) {
+ section = ;
+ }
+
+ return {section};
+}
diff --git a/src/components/RecommendationWizard.module.css b/src/components/RecommendationWizard.module.css
new file mode 100644
index 0000000..8c13db9
--- /dev/null
+++ b/src/components/RecommendationWizard.module.css
@@ -0,0 +1,20 @@
+article {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ margin: 5em;
+}
+
+legend {
+ background-color: #000;
+ color: #fff;
+ padding: 3px 6px;
+}
+
+button {
+ margin: 0.5em 0;
+}
+
+li {
+ list-style: none;
+}
\ No newline at end of file
diff --git a/src/data/questions.js b/src/data/questions.js
new file mode 100644
index 0000000..dabb5c5
--- /dev/null
+++ b/src/data/questions.js
@@ -0,0 +1,83 @@
+// Uses inquirer format (https://www.npmjs.com/package/inquirer#questions)
+export const questions = [
+ {
+ category: 'platformSupport',
+ message:
+ 'Does your application need to run on any mobile platform? (Select all that apply)',
+ type: 'checkbox',
+ dealBreaker: true,
+ choices: [
+ { name: 'Android', value: 'android' },
+ { name: 'iOS', value: 'ios' },
+ { name: 'None', value: 'none' },
+ ],
+ },
+ {
+ category: 'platformSupport',
+ message:
+ 'Does your application need to run on any desktop platform? (Select all that apply)',
+ type: 'checkbox',
+ dealBreaker: true,
+ choices: [
+ { name: 'Linux', value: 'linux' },
+ { name: 'macOS', value: 'macos' },
+ { name: 'Windows', value: 'windows' },
+ { name: 'None', value: 'none' },
+ ],
+ },
+ {
+ category: 'visual',
+ message:
+ 'Do you want your application to have a consistent look across platforms or do you want it to look closer to the Operating System?',
+ choices: [
+ { name: 'Consistent accross platforms', value: 'customUI' },
+ { name: 'Match the OS look and feel', value: 'platformUI' },
+ { name: 'Indifferent', value: 'none' },
+ ],
+ },
+ {
+ category: 'fieldType',
+ message:
+ 'Are you going to start a full new application or does it have to integrate with an existing one?',
+ choices: [
+ { name: 'New application', value: 'greenfield' },
+ { name: 'Existing application', value: 'brownfield' },
+ ],
+ },
+ {
+ category: 'targetAudience',
+ message: 'Who will be the main user of your application?',
+ choices: [
+ { name: 'Consumers', value: 'consumers' },
+ { name: 'Enterprise users', value: 'enterprise' },
+ ],
+ },
+ {
+ category: 'team',
+ notes: 'This depends mostly on enterprise users',
+ message:
+ 'Is the application going to have a team working fulltime in the longterm?',
+ choices: [
+ { name: 'Yes', value: 'longterm' },
+ { name: 'No', value: 'shortterm' },
+ ],
+ },
+ {
+ category: 'visual',
+ message:
+ "How visually complex or interactions is going to have your app's main view/page?",
+ choices: [
+ { name: 'Simple layout or interactions', value: 'simpleLayout' },
+ { name: 'Definitely not simple', value: 'complexLayout' },
+ ],
+ },
+ {
+ category: 'support',
+ message:
+ 'Do you think you will need to pay for support or would be help from the community be enough?',
+ choices: [
+ { name: 'Paid support', value: 'paidSupport' },
+ { name: 'Community', value: 'community' },
+ ],
+ },
+ ];
diff --git a/src/data/technologies.js b/src/data/technologies.js
new file mode 100644
index 0000000..0842de6
--- /dev/null
+++ b/src/data/technologies.js
@@ -0,0 +1,91 @@
+// TODO: This information should come from /data/technologies/${technology}.json directly instead of being handcrafted
+export const technologies = [
+ {
+ name: 'Electron',
+ normalizedName: 'electron',
+ categories: {
+ linux: 5,
+ windows: 5,
+ macos: 5,
+ android: 0,
+ ios: 0,
+ devEx: 3,
+ community: 3,
+ customUI: 5,
+ greenfield: 5,
+ brownfield: 2,
+ complexLayout: 4,
+ },
+ },
+ {
+ name: 'PWA',
+ normalizedName: 'pwa',
+ categories: {
+ linux: 5,
+ windows: 5,
+ macos: 5,
+ android: 4,
+ ios: 2,
+ devEx: 2,
+ community: 4,
+ customUI: 5,
+ platformUI: 0,
+ greenfield: 5,
+ complexLayout: 4,
+ },
+ },
+ {
+ name: 'Xamarin',
+ normalizedName: 'xamarin',
+ categories: {
+ linux: 0,
+ windows: 5,
+ macos: 4,
+ android: 3,
+ ios: 3,
+ devEx: 4,
+ paidSupport: 4,
+ community: 3,
+ customUI: 2,
+ platformUI: 4,
+ greenfield: 5,
+ complexLayout: 3,
+ },
+ },
+ {
+ name: 'WebView2',
+ normalizedName: 'webview2',
+ categories: {
+ linux: 0,
+ windows: 4,
+ macos: 0,
+ android: 0,
+ ios: 0,
+ devEx: 1,
+ paidSupport: 3,
+ community: 1,
+ customUI: 5,
+ greenfield: 5,
+ brownfield: 2,
+ complexLayout: 5,
+ },
+ },
+ {
+ name: 'React Native',
+ normalizedName: 'react-native',
+ categories: {
+ linux: 0,
+ windows: 3,
+ macos: 2,
+ android: 5,
+ ios: 5,
+ devEx: 3,
+ community: 4,
+ customUI: 2,
+ platformUI: 5,
+ greenfield: 5,
+ brownfield: 3,
+ complexLayout: 3,
+ },
+ },
+ ];
diff --git a/src/pages/recommendation.js b/src/pages/recommendation.js
new file mode 100644
index 0000000..a887efb
--- /dev/null
+++ b/src/pages/recommendation.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import clsx from 'clsx';
+import Layout from '@theme/Layout';
+import Link from '@docusaurus/Link';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import styles from './index.module.css';
+
+import RecommendationWizard from '../components/RecommendationWizard';
+
+function RecommendationHeader({ title, description }) {
+ const { siteConfig } = useDocusaurusContext();
+ return (
+
+
+
{title}
+
{description}
+
+
+ );
+}
+
+export default function Recommendation() {
+ const { siteConfig } = useDocusaurusContext();
+ const title = `Get recommendations for your next cross-platform project`;
+ const description = `Site still in beta!`;
+ return (
+
+
+
+
+
+
+ );
+}