Skip to content

Commit ca33e88

Browse files
authored
Merge pull request #618 from Stefterv/pde-open-exampes
Open examples in Processing
2 parents e4433b5 + 7666f87 commit ca33e88

File tree

10 files changed

+200
-5
lines changed

10 files changed

+200
-5
lines changed
Loading
Loading

scripts/updateExamples.js

+5
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const updateExamples = async () => {
7676
cwd: path.join(from, example.dirname)
7777
});
7878

79+
7980
for (let i = 0; i < pdes.length; i++) {
8081
const pde = pdes[i];
8182
fs.copySync(
@@ -91,6 +92,10 @@ const updateExamples = async () => {
9192

9293
for (let i = 0; i < dataFiles.length; i++) {
9394
const dataFile = dataFiles[i];
95+
fs.copySync(
96+
path.join(from, example.dirname, dataFile),
97+
path.join(to, example.dirname, dataFile)
98+
)
9499
fs.copySync(
95100
path.join(from, example.dirname, dataFile),
96101
path.join(

src/components/CopyButton.module.css

-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
.root {
2-
position: absolute;
3-
right: var(--gutter);
42
height: 32px;
53
padding: 6px 12px;
64
font-size: var(--text-small);

src/components/OpenWithButton.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { useState, useEffect } from 'react';
2+
import classnames from 'classnames';
3+
4+
import ProcessingIcon from '../images/logo-processing.svg';
5+
6+
import * as css from './OpenWithButton.module.css';
7+
8+
const OpenWithButton = ({ pdes, dataFiles }) => {
9+
const main = pdes[0]
10+
const [sketchURL, setSketchURL] = useState(`processing://open?sketch=${stringToBase64(main.code)}`)
11+
12+
useEffect(() => {
13+
let sketchURL = `pde://sketch/base64/${stringToBase64(main.code)}?pde=`
14+
for (let pde of pdes) {
15+
if (pde === main) continue
16+
sketchURL += `${pde.name}:${stringToBase64(pde.code)},`
17+
}
18+
if (dataFiles.length > 0) {
19+
let data = '&data=' + dataFiles.map(file => file.relativePath.split('/').pop() + ":" + window.location.protocol + "//" + window.location.host + file.publicURL).join(',')
20+
sketchURL += data
21+
}
22+
setSketchURL(sketchURL)
23+
}, [])
24+
const [showInstructions, setShowInstructions] = useState(false)
25+
26+
useEffect(() => {
27+
const handleClickOutside = (event) => {
28+
if (showInstructions && !event.target.closest(`.${css.root}`)) {
29+
setShowInstructions(false);
30+
}
31+
};
32+
33+
document.addEventListener('click', handleClickOutside);
34+
return () => document.removeEventListener('click', handleClickOutside);
35+
}, [showInstructions]);
36+
37+
return (
38+
<a
39+
href={sketchURL}
40+
type="button"
41+
className={classnames(css.root)}
42+
onClick={() => setShowInstructions(true)}
43+
>
44+
<ProcessingIcon /> {'Open In Processing'}
45+
{showInstructions && (
46+
<div className={classnames(css.instructions)}>
47+
<h1>Opening Processing<span className={css.ellipsis}></span></h1>
48+
<p>If nothing happens, <a href="https://www.processing.org/download/" target="_black">Download Processing</a> version 4.4.1 or later and try again.</p>
49+
<p className={classnames(css.tooltipFootnote)}>Make sure Processing is installed and was opened at least once.</p>
50+
</div>
51+
)}
52+
</a>
53+
)
54+
}
55+
56+
export default OpenWithButton;
57+
58+
function stringToBase64(str) {
59+
if (typeof Buffer !== 'undefined') {
60+
return Buffer.from(str).toString('base64');
61+
}
62+
return btoa(str)
63+
}
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
.root {
2+
height: 32px;
3+
padding: 6px 12px;
4+
font-size: var(--text-small);
5+
font-weight: bold;
6+
color: var(--darkgray);
7+
cursor: pointer;
8+
fill: var(--darkgray);
9+
position: relative;
10+
11+
& svg {
12+
height: 12px;
13+
width: auto;
14+
}
15+
}
16+
17+
.root:hover {
18+
color: var(--darkergray);
19+
fill: var(--darkergray);
20+
}
21+
22+
.root:active {
23+
color: var(--processing-blue-mid);
24+
fill: var(--processing-blue-mid);
25+
}
26+
27+
.instructions {
28+
bottom: 100%;
29+
margin-bottom: 16px;
30+
left: auto;
31+
right: 0;
32+
position: absolute;
33+
transform: none;
34+
min-width: 24em;
35+
max-width: 24em;
36+
width: fit-content;
37+
background-color: #555;
38+
color: #fff;
39+
text-align: left;
40+
border-radius: 6px;
41+
padding: 16px 10px 10px 25px;
42+
transition: opacity 0.3s;
43+
pointer-events: auto;
44+
a {
45+
color: var(--processing-blue-light);
46+
text-decoration: underline;
47+
&:hover {
48+
color: white;
49+
}
50+
}
51+
h1 {
52+
color: #fff;
53+
font-size: 2em;
54+
margin: 0 0 0.3em 0;
55+
font-weight: 500;
56+
}
57+
&::before {
58+
content: "";
59+
position: absolute;
60+
top: 100%;
61+
left: auto;
62+
right: 20px;
63+
margin-left: -10px;
64+
border-width: 10px;
65+
border-style: solid;
66+
border-color: #555 transparent transparent transparent;
67+
}
68+
}
69+
70+
.tooltipFootnote {
71+
color: var(--gray);
72+
font-size: 0.9em;
73+
}
74+
75+
@keyframes ellipsis {
76+
10% {
77+
content: " ";
78+
}
79+
25% {
80+
content: " . ";
81+
}
82+
50% {
83+
content: " .. ";
84+
}
85+
75% {
86+
content: " ...";
87+
}
88+
}
89+
90+
.ellipsis::after {
91+
display: inline-block;
92+
animation: ellipsis 1.5s infinite;
93+
content: " ...";
94+
}

src/components/Tabs.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import classnames from 'classnames';
33

44
import Button from './Button';
55
import CopyButton from './CopyButton';
6+
import OpenWithButton from './OpenWithButton';
67

78
import { useHighlight } from '../hooks';
89
import { escapeHtml } from '../utils';
910

1011
import * as css from './Tabs.module.css';
1112

12-
const Tabs = ({ pdes, className }) => {
13+
const Tabs = ({ pdes, className, dataFiles }) => {
1314
const [active, setActive] = useState(pdes[0].name);
1415

1516
useHighlight();
@@ -40,7 +41,10 @@ const Tabs = ({ pdes, className }) => {
4041
[css.activeCode]: pde.name === active
4142
})}
4243
key={key}>
43-
<CopyButton text={pde.code} />
44+
<div className={css.actions}>
45+
<CopyButton text={pde.code} />
46+
<OpenWithButton pdes={pdes} dataFiles={dataFiles} />
47+
</div>
4448
<pre className={css.codeBlock}>
4549
<code
4650
dangerouslySetInnerHTML={{

src/components/Tabs.module.css

+5
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@
3737
padding-bottom: var(--gutter-double);
3838
white-space: break-spaces;
3939
}
40+
41+
.actions{
42+
position: absolute;
43+
right: var(--gutter);
44+
}

src/hooks/examples/data.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useStaticQuery, graphql } from "gatsby"
2+
3+
export const useExampleDataFiles = (sketchName) => {
4+
const data = useStaticQuery(
5+
graphql`
6+
query GetExampleDataFiles {
7+
allFile(
8+
filter: {sourceInstanceName: {eq: "examples"}, name: {}, relativePath: {regex: "/data/"}}
9+
) {
10+
edges {
11+
node {
12+
relativePath
13+
publicURL
14+
}
15+
}
16+
}
17+
}`
18+
)
19+
return data.allFile.edges
20+
.map((edge) => edge.node)
21+
.filter(node => node.relativePath.includes(sketchName))
22+
;
23+
}

src/templates/examples/example.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import {
2222
useRelatedExamples,
2323
useTrail
2424
} from '../../hooks/examples';
25+
import { useExampleDataFiles } from '../../hooks/examples/data';
2526

2627
import * as css from '../../styles/templates/examples/example.module.css';
2728
import * as grid from '../../styles/grid.module.css';
2829

30+
2931
const ExampleTemplate = ({ data, pageContext }) => {
3032
const [showSidebar, setShowSidebar] = useSidebar('examples');
3133
const intl = useIntl();
@@ -35,6 +37,7 @@ const ExampleTemplate = ({ data, pageContext }) => {
3537
const { image, allExamples, relatedImages, liveSketch } = data;
3638

3739
const example = usePreparedExample(data.example);
40+
const dataFiles = useExampleDataFiles(name)
3841
const pdes = usePdes(data.pdes.nodes, locale, name);
3942
const examples = usePreparedExamples(allExamples.nodes, relatedImages.nodes);
4043
const tree = useTree(examples);
@@ -117,7 +120,7 @@ const ExampleTemplate = ({ data, pageContext }) => {
117120
/>
118121
)}
119122
</div>
120-
<Tabs pdes={pdes} />
123+
<Tabs pdes={pdes} dataFiles={dataFiles} />
121124
<RelatedExamples
122125
examples={relatedExamples}
123126
heading={intl.formatMessage({ id: 'relatedExamples' })}

0 commit comments

Comments
 (0)