Skip to content
Open
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
Binary file modified docs/example.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 3 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 76 additions & 1 deletion src/css/index.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
:root {
--black: #000;
--blue: #3b8be4;
--indigo: #6610f2;
--purple: #703c87;
Expand All @@ -21,7 +22,7 @@
--danger: var(--red);
--text-color: var(--gray-dark);
--text-muted: var(--gray-light);
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--font-family-sans-serif: 'Nunito Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--font-family-monospace: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace;
}

Expand All @@ -32,3 +33,77 @@ body {
color: var(--text-color);
margin: 200px 50px;
}

div.component-photo-tree {
width: 830px;
}

div.component-photo-tree ul {
list-style-type: none;
padding-left: 0;
}

div.component-photo-tree ul ul {
padding-left: 40px;
}

div.component-photo-tree ul li.collapsed li {
display: none;
}

div.component-photo-tree ul li div.line-data {
margin: 5px 0;
padding: 5px;
background-color: #EBEAED;
border-radius: 5px;
border: 1px solid #DEDEDE;
font-size: 13px;
display: flex;
align-items: center; /* align vertical */
outline: none;
}

div.component-photo-tree ul li div.line-data span.node-name {
margin-left: 10px;
}

div.component-photo-tree ul li div.line-data.has-children {
cursor: pointer;
}

div.component-photo-tree ul li div.line-data img {
border-radius: 3px;
width: 25px;
height: 25px;
}

div.component-photo-tree ul li span.icon-down {
display: inline-block;
}

div.component-photo-tree ul li span.icon-right {
display: none;
}

div.component-photo-tree ul li span.expanders.collapsed span.icon-down {
display: none;
}

div.component-photo-tree ul li span.expanders.collapsed span.icon-right {
display: inline-block;
}

div.component-photo-tree ul li span.icon-right,
div.component-photo-tree ul li span.icon-down {
font-size: 20px;
}


body.theme-dark-mode {
color: var(--white);
background-color: var(--black);
}

body.theme-dark-mode div.component-photo-tree ul li div.line-data {
background-color: var(--gray-dark);
}
2 changes: 2 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<html lang="en">
<head>
<link rel="stylesheet" href="./src/css/index.css">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300&display=swap" rel="stylesheet">
</head>
<body>
<div id="container"></div>
Expand Down
42 changes: 36 additions & 6 deletions src/js/App.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import React from 'react'
import React, { useState, useEffect, createContext } from 'react'

import AppContext from './AppContext'
import ColorSchemeSwitcher from './ColorSchemeSwitcher'
import PhotoTree from './PhotoTree'

// NOTE: ESLint will enforce Táve coding standards:
// https://github.com/tave/javascript/ Goodbye semicolons!

function App() {
const webserviceUrl = 'http://localhost:8000/webservice.php'
const [isDarkMode, setIsDarkMode] = useState(null)

// set dark mode setting.
function initializeColorSchemeSwitcher() {
fetch(`${webserviceUrl}?action=getSetting&name=dark_mode`)
.then(response => response.json())
.then(setIsDarkMode)
// .catch(app/component-specific handler as appropriate)
}

// get dark mode setting.
function updateChangedColorScheme(darkModeVal) {
const darkModeParamVal = darkModeVal === 'darkMode' ? 1 : 0

fetch(`${webserviceUrl}?action=setSetting&name=dark_mode&value=${darkModeParamVal}`)
setIsDarkMode(!!darkModeParamVal)
}

useEffect(initializeColorSchemeSwitcher, [])

return (
<div>
<h1>Hello World!</h1>
</div>
<AppContext.Provider
value={{ webserviceUrl, isDarkMode }}
>
<div className="component-app">
<ColorSchemeSwitcher updateChangedColorScheme={updateChangedColorScheme} />
<br />
<PhotoTree />
</div>
</AppContext.Provider>
)
}


export default App
5 changes: 5 additions & 0 deletions src/js/AppContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React, { createContext } from 'react'

const AppContext = createContext(null)

export default AppContext
51 changes: 51 additions & 0 deletions src/js/ColorSchemeSwitcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useContext } from 'react'
import PropTypes from 'prop-types'

import AppContext from './AppContext'


function ColorSchemeSwitcher({ updateChangedColorScheme }) {
const { isDarkMode } = useContext(AppContext)

// set theme on body if non-null.
const body = document.getElementsByTagName('body')[0]
body.classList.remove('theme-light-mode', 'theme-dark-mode')

if (isDarkMode !== null) {
if (isDarkMode) {
body.classList.add('theme-dark-mode')
}
else {
body.classList.add('theme-light-mode')
}
}

// set visibility to hidden rather than display none. this prevents:
// (1) flash of incorrect setting within select box before settings loaded from server
// (2) page elements jumping around when setting is loaded after element set to display: none
return (
<div className="component-color-scheme-switcher">
<label htmlFor="colorScheme">
Color Scheme Switcher:
<br />
<select
name="colorScheme"
value={isDarkMode ? 'darkMode' : 'lightMode'}
onChange={e => updateChangedColorScheme(e.target.value)}
style={{ visibility: isDarkMode === null ? 'hidden' : 'visible' }}
>
<option value="">-- select --</option>
<option value="lightMode">Light Mode</option>
<option value="darkMode">Dark Mode</option>
</select>
</label>
</div>
)
}

ColorSchemeSwitcher.propTypes = {
updateChangedColorScheme: PropTypes.func.isRequired,
}


export default ColorSchemeSwitcher
88 changes: 88 additions & 0 deletions src/js/PhotoTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useState, useEffect, useContext } from 'react'

import PhotoTreeList from './PhotoTreeList'
import AppContext from './AppContext'


function PhotoTree() {
const [nodes, setNodes] = useState([])
const { webserviceUrl } = useContext(AppContext)

// build out nested structure from node list for recursive rendering.
function getRecursiveTree(_nodes) {
// index by node.id
let tree = {}
for (let i = 0; i < _nodes.length; i += 1) {
const node = _nodes[i]
tree[node.id] = node
}

// take advantage of the fact that objects are references
// so they can be both iterated over and moved within their parent element.
for (let i = 0; i < _nodes.length; i += 1) {
const node = _nodes[i]

if (node.parent !== null) {
if (typeof tree[node.parent].children === 'undefined') {
tree[node.parent].children = {}
}

tree[node.parent].children[node.id] = node
}
}

// delete the top-level item object refs in the object that don't have a parent
// (since they've been moved under their correct parents at this point)
for (let i = 0; i < _nodes.length; i += 1) {
if (_nodes[i].parent !== null) {
delete tree[_nodes[i].id]
}
}

// convert the recursive object to an array since there is no
// react PropTypes.objectOf style validation.
function convertToArray(_tree) {
const treeAr = []
let i = 0

Object.values(_tree).forEach((value) => {
treeAr[i] = value
if (treeAr[i].children) {
treeAr[i].children = convertToArray(treeAr[i].children)
}
else {
treeAr[i].children = []
}
i += 1
})

return treeAr
}

tree = convertToArray(tree)

return tree
}

function initializePhotoTree() {
fetch(`${webserviceUrl}?action=getNodes`)
.then(response => response.json())
.then((data) => {
setNodes(getRecursiveTree(data))
})
// .catch(app/component-specific handler as appropriate)
}

useEffect(initializePhotoTree, [])


return (
<div className="component-photo-tree">
Photo Tree:
<PhotoTreeList nodes={nodes} />
</div>
)
}


export default PhotoTree
Loading