diff --git a/src/assets/scss/App.scss b/src/assets/scss/App.scss index e73247a..4252fd3 100644 --- a/src/assets/scss/App.scss +++ b/src/assets/scss/App.scss @@ -1,7 +1,6 @@ @import "./vars"; .App { - padding: 10px; box-sizing: border-box; font-family: "Montserrat", sans-serif; font-size: 12px; @@ -22,22 +21,31 @@ align-items: center; margin-bottom: 7px; margin-top: 7px; + &.location-picker-label { + margin-top: 12px; + } + &.latitude { + margin-bottom:12px; + } } .tab-content { - //display: none; would be easier, but it somehow Seasons 3D view doesn't initialize itself properly + // display: none; would be easier, but it somehow Seasons 3D view doesn't initialize itself properly // when display: none; is used. visibility: hidden; width: 0; height: 0; position: absolute; - padding: 3px; - border-top: 1px solid #777777; + padding-left: 9px $edge-padding $edge-padding $edge-padding; + // note each .tab-content is a wrapper for tab.location, tab.simulation, tab.about + // eahc of which maintain their own static layout widths + // this is 100% wide to cover the space affored by CODAP window whose width can change &.active { + background-color: white; visibility: visible; - width: auto; - height: auto; + width: 100%; + height: 100%; } } } diff --git a/src/assets/scss/about-tab.scss b/src/assets/scss/about-tab.scss new file mode 100644 index 0000000..9da87cb --- /dev/null +++ b/src/assets/scss/about-tab.scss @@ -0,0 +1 @@ +@import "./vars.scss"; diff --git a/src/assets/scss/header.scss b/src/assets/scss/header.scss index 831e7fc..80acea6 100644 --- a/src/assets/scss/header.scss +++ b/src/assets/scss/header.scss @@ -1,64 +1,65 @@ @import "./vars.scss"; -.header { - p { - margin: 0; - } - - .info-icon { - position: absolute; - top: 7px; - right: 7px; - } - - .plugin-info-popup { - display: flex; - transform: translateX(-50px); - position: absolute; - background-color: white; - box-shadow: 0px 2px 4px rgba(silver, 0.4); - padding: 10px; - border-radius: 4px; - border: 1.5px solid rgba($codap-teal, 0.3); - width: 150px; - top: 30px; - right: -20px; - &.hidden { - display: none; - } - } -} +$tab-height: 26px; +$general-tab-width: 80px; +$location-tab-width: 74px; +$simulation-tab-width: 88px; +$about-tab-width: 60px; .tab-container { - display: flex; + background-color: white; + padding-top:$edge-padding + 4px; + padding-left: $edge-padding; + padding-right: $edge-padding; width: 100%; margin-bottom: 0; position: relative; z-index: 1; - justify-content: flex-start; + border-bottom:1px solid #222; + height: $tab-height; + display: flex; .tab { - flex: 0 0 auto; + position: absolute; text-align: center; - padding: 10px 20px; + padding: 4px $tab-padding; cursor: pointer; - color: #666; - border: 1px solid $codap-teal; - border-bottom: none; + color: #222; + border: 1px solid #222; margin-right: -1px; - position: relative; - border-radius: 8px 8px 0 0; - background-color: #e9e9e9; + border-radius: 4px 4px 0 0; + display:inline-block; + bottom:-1px; + background-color: #f0f0f0; &.active { background-color: white; - color: $codap-teal; border-bottom: 1px solid white; - z-index: 2; + font-weight: bold; + z-index: 3; + height: 18px; } &:hover:not(.active) { background-color: #e0e0e0; } + + &.location { + left: $edge-padding; + } + + &.simulation { + left: $edge-padding + $location-tab-width; + &.disabled { + color: silver; + } + } + + &.about { + left: $edge-padding + $location-tab-width + $simulation-tab-width; + &.active { + left: $edge-padding + $location-tab-width + $simulation-tab-width - 1px; + } + } } } diff --git a/src/assets/scss/location-picker.scss b/src/assets/scss/location-picker.scss index cd35edb..195833b 100644 --- a/src/assets/scss/location-picker.scss +++ b/src/assets/scss/location-picker.scss @@ -2,10 +2,11 @@ .location-picker-label { position: relative; + margin-top:6px; .location-icon { position: absolute; left: 4px; - top: 26px; + top: 27px; z-index: 1; } } @@ -13,15 +14,15 @@ .location-picker { display:flex; position: relative; - margin-bottom: 12px; justify-content: space-between; input { padding: 5px; + height: $input-height; width: 100%; - border-radius: 4px; + border-radius: $input-border-radius; box-sizing: border-box; - border: 1.5px solid rgba($codap-teal, 0.3); + border: $input-border; padding-left: 25px; outline: none; &:active, &:focus { diff --git a/src/assets/scss/location-tab.scss b/src/assets/scss/location-tab.scss index 0ce6aa3..a4a05fa 100644 --- a/src/assets/scss/location-tab.scss +++ b/src/assets/scss/location-tab.scss @@ -1,41 +1,64 @@ @import "./vars"; .location-tab { - width: 350px; - padding:10px; + box-sizing: border-box; + width: $location-tab-width; + height: $location-tab-height; + padding: $tab-padding; + + .intro { + padding-top: 6px; + padding-bottom: 6px; + } hr { width: 100%; - margin: 10px 0; - border: none; - border-top: 1.5px solid rgba($codap-teal, 0.3); + border-top: 1px solid #aaa; + &.light { + border-top: 1px solid $codap-teal-light; + } + &.above-attrs { + margin-top:22px; + margin-bottom:20px; + } } .latitude, .longitude { - justify-content: space-between; + display: flex; + align-items: center; + label { + margin-left: auto; + text-align: right; + margin-right: 10px; + } input { - width: 250px; - padding: 5px; - border-radius: 4px; + width: 236px; + padding: 7px 8px; + border-radius: $input-border-radius; box-sizing: border-box; - border: 1.5px solid rgba($codap-teal, 0.3); + border: $input-border; } } + .or-container { + box-sizing: border-box; + position: relative; + text-align: left; + padding: $tab-padding 0px; + } + .or { - display: none; // TODO: bring this or element back + display: inline-block; + position: absolute; + top: 11px; background-color: white; - width: 40px; - padding:0px 10px; + width: 30px; + padding:0px $tab-padding + 4px; color: $codap-teal; font-weight: bold; } - .latitude { - margin-top: 12px; - } - .attributes-selection { display: flex; flex-direction: column; @@ -49,11 +72,11 @@ display: flex; flex-wrap: wrap; width: 100%; - margin-top: 10px; + margin-top: $tab-padding; gap: 3px; li.token { - padding: 6px 12px; + padding: 8px $tab-padding; border-radius: 20px; cursor: pointer; transition: background-color 0.3s, color 0.3s; @@ -63,16 +86,17 @@ &.on { background-color: $codap-teal-lightest; - color: $codap-teal; border: 1px solid $codap-teal; } &.off { background-color: white; - color: #aaa; border: 1px solid $codap-teal; } } + li.token:hover { + background-color: $codap-teal-lightest; + } } } @@ -81,10 +105,11 @@ button { background-color: white; - padding: 6px 12px; + font-family: "Montserrat", sans-serif; + font-weight: medium; + padding: 9px 12px; border-radius: 4px; margin-left: 10px; - cursor: pointer; border: 1px solid $codap-teal; color: $codap-teal; transition: background-color 0.3s, color 0.3s; @@ -98,6 +123,7 @@ &:hover:not(:disabled) { background-color: $codap-teal-light; color: white; + cursor: pointer; } } } diff --git a/src/assets/scss/simulation-tab.scss b/src/assets/scss/simulation-tab.scss index 52cba15..b34c2f1 100644 --- a/src/assets/scss/simulation-tab.scss +++ b/src/assets/scss/simulation-tab.scss @@ -1,6 +1,9 @@ @import "./vars.scss"; .simulation-tab { + padding: $tab-padding; + width: $sim-tab-width; + height: $sim-tab-height; .get-data-button { position: relative; top: -15px; diff --git a/src/assets/scss/vars.scss b/src/assets/scss/vars.scss index 2a3839b..ea296ac 100644 --- a/src/assets/scss/vars.scss +++ b/src/assets/scss/vars.scss @@ -1,3 +1,20 @@ $codap-teal: #177991; $codap-teal-light: #72bfca; -$codap-teal-lightest: rgba(114, 191, 202, 0.25); \ No newline at end of file +$codap-teal-lightest: rgba(114, 191, 202, 0.25); +$edge-padding: 4px; + +$location-tab-width: 340px; +$location-tab-height: 630px; + +$sim-tab-width: 690px; +$sim-tab-height: $location-tab-height; + +$about-tab-width: $location-tab-width; +$about-tab-height: $location-tab-height; + +$tab-padding: 10px; +$input-height: 32px; +$input-margin: 0 0 12px 8px; +$input-border-radius: 3px; +$input-border: solid 1px rgba(23, 121, 145, 0.75); + diff --git a/src/components/App.tsx b/src/components/App.tsx index 2414d79..2d9fb89 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,12 +1,13 @@ import React, { useEffect, useState, useRef, useCallback } from "react"; import { clsx } from "clsx"; -import { ICurrentDayLocation, ILocation } from "../types"; +import { ICurrentDayLocation, ILocation, TabName } from "../types"; import { debounce } from "../grasp-seasons/utils/utils"; import { kInitialDimensions, kVersion, kPluginName, kDefaultOnAttributes, kSimulationTabDimensions, kDataContextName, kChildCollectionName } from "../constants"; import { initializePlugin, codapInterface, selectSelf, addDataContextChangeListener, ClientNotification, getCaseByID } from "@concord-consortium/codap-plugin-api"; import { useCodapData } from "../hooks/useCodapData"; import { LocationTab } from "./location-tab"; import { SimulationTab } from "./simulation-tab"; +import { AboutTab } from "./about-tab"; import { Header } from "./header"; import { locationsEqual } from "../utils/daylight-utils"; @@ -41,14 +42,20 @@ const debouncedUpdateRowSelectionInCodap = debounce(( }, 250); export const App: React.FC = () => { - const [activeTab, setActiveTab] = useState<"location" | "simulation">("location"); + const [activeTab, setActiveTab] = useState("location"); const [latitude, setLatitude] = useState(""); const [longitude, setLongitude] = useState(""); const [dayOfYear, setDayOfYear] = useState(171); const [locations, setLocations] = useState([]); const [locationSearch, setLocationSearch] = useState(""); const [selectedAttrs, setSelectedAttributes] = useState(kDefaultOnAttributes); - const [dataContext, setDataContext] = useState(null); + const [simEnabled, setSimEnabled] = useState(false); + + const { getDayLengthData, dataContext, getUniqueLocationsInCodapData } = useCodapData(); + + useEffect(() => { + setSimEnabled(!!dataContext); + }, [dataContext]); const currentDayLocationRef = useRef({ _latitude: "", @@ -56,9 +63,10 @@ export const App: React.FC = () => { _dayOfYear: 171 }); - const { getUniqueLocationsInCodapData } = useCodapData(); const getUniqueLocationsRef = useRef(getUniqueLocationsInCodapData); + // TODO handle click on the body to move plugin to front + const handleDayUpdateInTheSimTab = (day: number) => { currentDayLocationRef.current._dayOfYear = day; debouncedUpdateRowSelectionInCodap( @@ -127,6 +135,7 @@ export const App: React.FC = () => { ); } } + console.log("|| App: dataContextChange", values); }, [handleCaseSelectionInCodap]); useEffect(() => { @@ -141,7 +150,9 @@ export const App: React.FC = () => { }; }, [latitude, longitude, dayOfYear]); - const handleTabClick = (tab: "location" | "simulation") => { + const handleTabClick = (tab: TabName) => { + // comment out next line during development + if (tab === "simulation" && !simEnabled) return; setActiveTab(tab); codapInterface.sendRequest({ action: "update", @@ -157,8 +168,6 @@ export const App: React.FC = () => { }); }; - const { getDayLengthData } = useCodapData(); - const handleGetDataClick = async () => { const name = locationSearch || `(${latitude}, ${longitude})`; const currentLocation: ILocation = { name, latitude: Number(latitude), longitude: Number(longitude) }; @@ -179,6 +188,7 @@ export const App: React.FC = () => {
{ setLongitude={setLongitude} setLocationSearch={setLocationSearch} setSelectedAttributes={setSelectedAttributes} - setDataContext={setDataContext} - locations={locations} setLocations={setLocations} handleGetDataClick={handleGetDataClick} /> @@ -211,6 +219,9 @@ export const App: React.FC = () => { handleGetDataClick={handleGetDataClick} />
+
+ +
); }; diff --git a/src/components/about-tab.tsx b/src/components/about-tab.tsx new file mode 100644 index 0000000..db39291 --- /dev/null +++ b/src/components/about-tab.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +import "../assets/scss/about-tab.scss"; + +export const AboutTab: React.FC = () => { + return ( +
+

about

+
+ ); +}; + diff --git a/src/components/header.tsx b/src/components/header.tsx index 887c838..53a470c 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -1,49 +1,35 @@ -import React, { useState } from "react"; -import InfoIcon from "../assets/images/icon-info.svg"; +import React from "react"; +import { TabName } from "../types"; import "../assets/scss/header.scss"; interface IHeaderProps { - activeTab: "location" | "simulation"; - onTabClick: (tab: "location" | "simulation") => void; + activeTab: TabName; + onTabClick: (tab: TabName) => void; + showEnabled: boolean; } -export const Header: React.FC = ({ activeTab, onTabClick }) => { - const [showInfo, setShowInfo] = useState(false); - - const handleInfoClick = () => { - setShowInfo(!showInfo); - }; - +export const Header: React.FC = ({ activeTab, onTabClick, showEnabled }) => { return ( - <> -
-

- How long is a day?
- Enter a location or coordinates to retrieve data -

- - - -
- plugin info -
+
+
onTabClick("location")} + > + Location +
+
onTabClick("simulation")} + > + Simulation
-
-
-
onTabClick("location")} - > - Location -
-
onTabClick("simulation")} - > - Simulation -
+
onTabClick("about")} + > + About
- +
); }; diff --git a/src/components/location-picker.tsx b/src/components/location-picker.tsx index cd9341a..a36e6b4 100644 --- a/src/components/location-picker.tsx +++ b/src/components/location-picker.tsx @@ -95,7 +95,7 @@ export const LocationPicker: React.FC = ({
void; setLocationSearch: (search: string) => void; setSelectedAttributes: (attrs: string[]) => void; - setDataContext: (context: any) => void; setLocations: (locations: ILocation[]) => void; handleGetDataClick: (latitude: string, longitude: string) => void; } @@ -27,7 +25,6 @@ export const LocationTab: React.FC = ({ longitude, locationSearch, selectedAttrs, - locations, setLatitude, setLongitude, setLocationSearch, @@ -36,6 +33,8 @@ export const LocationTab: React.FC = ({ handleGetDataClick }) => { + const enableGetData = latitude !== "" && longitude !== ""; + const { dataContext, handleClearData, @@ -88,18 +87,25 @@ export const LocationTab: React.FC = ({ return (
+
+ How long is a day?
+ Enter a location or coordinates to retrieve data +
+
-
OR
-
+
+
+ OR +
@@ -108,12 +114,12 @@ export const LocationTab: React.FC = ({
-
+
    @@ -129,12 +135,13 @@ export const LocationTab: React.FC = ({ ))) }
+
-
diff --git a/src/constants.ts b/src/constants.ts index b4fafdc..b240c13 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,8 +2,8 @@ export const kPluginName = "Day Length Plugin"; export const kVersion = "0.0.1"; export const kDataContextName = "DayLengthPluginData"; export const kInitialDimensions = { - width: 380, - height: 680 + width: 340, + height: 546 }; export const kSimulationTabDimensions = { width: 690, diff --git a/src/hooks/useCodapData.ts b/src/hooks/useCodapData.ts index 654fa92..0c7174e 100644 --- a/src/hooks/useCodapData.ts +++ b/src/hooks/useCodapData.ts @@ -33,7 +33,6 @@ export const useCodapData = () => { }; const getDayLengthData = async (location: ILocation) => { - let createDC; const calcOptions: DaylightCalcOptions = { latitude: location.latitude, longitude: location.longitude, @@ -43,12 +42,12 @@ export const useCodapData = () => { const solarEvents = getDayLightInfo(calcOptions); const existingDataContext = await getDataContext(kDataContextName); + let newDataContext; if (!existingDataContext.success) { - createDC = await createDataContext(kDataContextName); - setDataContext(createDC.values); + newDataContext = await createDataContext(kDataContextName); } - if (existingDataContext?.success || createDC?.success) { + if (existingDataContext?.success || newDataContext?.success) { await createParentCollection( kDataContextName, kParentCollectionName, @@ -61,25 +60,22 @@ export const useCodapData = () => { kChildCollectionAttributes ); - const completeSolarRecords = solarEvents.map(solarEvent => { - const record: Record = { - latitude: location.latitude, - longitude: location.longitude, - location: location.name, - date: solarEvent.day, - dayOfYear: solarEvent.dayOfYear, - rawSunrise: solarEvent.rawSunrise, - rawSunset: solarEvent.rawSunset, - "Day length": solarEvent.dayLength, - "Season": solarEvent.season, - "Sunlight angle": solarEvent.sunlightAngle, - "Solar intensity": solarEvent.solarIntensity - }; - - return record; - }); + const completeSolarRecords = solarEvents.map(solarEvent => ({ + latitude: location.latitude, + longitude: location.longitude, + location: location.name, + date: solarEvent.day, + dayOfYear: solarEvent.dayOfYear, + rawSunrise: solarEvent.rawSunrise, + rawSunset: solarEvent.rawSunset, + "Day length": solarEvent.dayLength, + "Season": solarEvent.season, + "Sunlight angle": solarEvent.sunlightAngle, + "Solar intensity": solarEvent.solarIntensity + })); await createItems(kDataContextName, completeSolarRecords); + setDataContext(existingDataContext.success ? existingDataContext.values : newDataContext?.values); return await createTable(kDataContextName); } }; @@ -131,6 +127,7 @@ export const useCodapData = () => { return { dataContext, + setDataContext, updateAttributeVisibility, handleClearData, getDayLengthData, diff --git a/src/types.ts b/src/types.ts index 515f92b..722537f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -38,3 +38,4 @@ export interface GeoNameSearchOptions { maxRows?: number; } +export type TabName = "location" | "simulation" | "about";