diff --git a/electron/package.json b/electron/package.json index 8bc4b53a..5e126a2b 100644 --- a/electron/package.json +++ b/electron/package.json @@ -53,7 +53,7 @@ "eslint-plugin-import": "^2.22.1" }, "dependencies": { - "@lightningrodlabs/electron-holochain": "=0.7.8", + "@lightningrodlabs/electron-holochain": "=0.7.12", "electron-context-menu": "^3.5.0", "electron-default-menu": "^1.0.2", "electron-log": "^4.3.5", diff --git a/electron/src/holochain.ts b/electron/src/holochain.ts index 73cffc22..fba31ccb 100644 --- a/electron/src/holochain.ts +++ b/electron/src/holochain.ts @@ -1,9 +1,6 @@ -import * as path from 'path' -import { app } from 'electron' import { ElectronHolochainOptions, StateSignal, - PathOptions, } from '@lightningrodlabs/electron-holochain' import { DATASTORE_PATH, KEYSTORE_PATH, PROFILES_HAPP_PATH } from './paths' @@ -53,6 +50,7 @@ const devOptions: ElectronHolochainOptions = { keystorePath: KEYSTORE_PATH, passphrase: 'test-passphrase', bootstrapUrl: 'https://bootstrap.holo.host', + logging: 'Json', } const prodOptions: ElectronHolochainOptions = { happPath: PROFILES_HAPP_PATH, // preload @@ -63,6 +61,7 @@ const prodOptions: ElectronHolochainOptions = { keystorePath: KEYSTORE_PATH, passphrase: 'test-passphrase', bootstrapUrl: 'https://bootstrap.holo.host', + logging: 'Json', } export { devOptions, prodOptions } diff --git a/electron/src/index.ts b/electron/src/index.ts index 43bb52d3..1dcd2604 100644 --- a/electron/src/index.ts +++ b/electron/src/index.ts @@ -1,17 +1,13 @@ -import { - app, - BrowserWindow, - ipcMain, - shell, - autoUpdater, - Menu, -} from 'electron' +import { app, BrowserWindow, ipcMain, shell, autoUpdater, Menu } from 'electron' import * as contextMenu from 'electron-context-menu' import * as path from 'path' import * as fs from 'fs' import initAgent, { + ERROR_EVENT, StateSignal, STATUS_EVENT, + HOLOCHAIN_LOG_EVENT, + WASM_LOG_EVENT, } from '@lightningrodlabs/electron-holochain' import { devOptions, prodOptions, stateSignalToText } from './holochain' @@ -45,8 +41,6 @@ contextMenu.default({ showSaveImageAs: true, }) - - // Handle creating/removing shortcuts on Windows when installing/uninstalling. // if (require('electron-squirrel-startup')) { // eslint-disable-line global-require @@ -177,19 +171,37 @@ app.on('ready', async () => { const splashWindow = createSplashWindow() const opts = app.isPackaged ? prodOptions : devOptions const { statusEmitter, shutdown } = await initAgent(app, opts, BINARY_PATHS) + let mainWindow: BrowserWindow | null = null statusEmitter.on(STATUS_EVENT, (state: StateSignal) => { switch (state) { case StateSignal.IsReady: // important that this line comes before the next one // otherwise this triggers the 'all-windows-closed' // event - createMainWindow() + mainWindow = createMainWindow() splashWindow.close() break default: splashWindow.webContents.send('status', stateSignalToText(state)) } }) + statusEmitter.on(ERROR_EVENT, (error: Error) => { + if (!error.message.includes('no project meta exists')) { + if (mainWindow) { + mainWindow.webContents.send('holochainError', error.message) + } + } + }) + statusEmitter.on(HOLOCHAIN_LOG_EVENT, (log: string) => { + if (mainWindow) { + mainWindow.webContents.send('holochainLog', log) + } + }) + statusEmitter.on(WASM_LOG_EVENT, (log: string) => { + if (mainWindow) { + mainWindow.webContents.send('wasmLog', log) + } + }) }) // Quit when all windows are closed, except on macOS. There, it's common @@ -285,7 +297,7 @@ ipcMain.handle('checkForMigrationData', (event) => { ) return { file: existingMigrationPath, - data: prevVersionMigrationDataString + data: prevVersionMigrationDataString, } } else { return null diff --git a/web/src/components/ButtonCheckbox/ButtonCheckbox.scss b/web/src/components/ButtonCheckbox/ButtonCheckbox.scss index b879d730..63ee2f48 100644 --- a/web/src/components/ButtonCheckbox/ButtonCheckbox.scss +++ b/web/src/components/ButtonCheckbox/ButtonCheckbox.scss @@ -14,10 +14,10 @@ transition: 0.2s box-shadow ease; -webkit-font-smoothing: antialiased; user-select: none; - border: 0.15rem solid #ffffff00; + border: 0.15rem solid var(--border-color-button-checkbox); &:hover, - &.focused { + &:focus { box-shadow: 0rem 0rem 1.25rem var(--shadow-color-hover); } @@ -45,6 +45,7 @@ .button-checkbox-text { user-select: none; + margin-right: 0.5rem; /* Standard */ } } diff --git a/web/src/components/ButtonCheckbox/ButtonCheckbox.tsx b/web/src/components/ButtonCheckbox/ButtonCheckbox.tsx index 69054016..858f98ce 100644 --- a/web/src/components/ButtonCheckbox/ButtonCheckbox.tsx +++ b/web/src/components/ButtonCheckbox/ButtonCheckbox.tsx @@ -1,6 +1,5 @@ -import React from 'react' +import React, { useEffect } from 'react' import Checkbox from '../Checkbox/Checkbox' -import Icon from '../Icon/Icon' import './ButtonCheckbox.scss' @@ -19,8 +18,23 @@ const ButtonCheckbox: React.FC = ({ icon, text, }) => { + useEffect(() => { + // listen for Enter key to be pressed, but + // ignore if Command/Ctrl is also pressed + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter' && !e.metaKey) { + onChange(!isChecked) + } + } + document.addEventListener('keydown', handleKeyDown) + return () => { + document.removeEventListener('keydown', handleKeyDown) + } + }, [isChecked, onChange]) return (
- + {pendingText} ) : ( diff --git a/web/src/components/CreateProjectModal/CreateProjectModal.scss b/web/src/components/CreateProjectModal/CreateProjectModal.scss index 732cb194..10b12ebb 100644 --- a/web/src/components/CreateProjectModal/CreateProjectModal.scss +++ b/web/src/components/CreateProjectModal/CreateProjectModal.scss @@ -1,79 +1,32 @@ -.create-project-modal-wrapper { - /* width: 550px; */ - height: 523px; - overflow: hidden; +.create-project-modal { + width: 30rem; + overflow: auto; position: relative; justify-content: space-between; align-items: center; -} - -.create-project-modal-wrapper .validating-form-input { - margin-bottom: 28px; -} - -.project-modal-content { - line-height: 1.45; -} - -.create-project-image-row { - display: flex; - flex-direction: row; - align-items: flex-end; -} - -.create-project-image-row .validating-form-input { - margin-bottom: 0; -} - -.create-project-image { - margin-left: 8px; - width: 44px; - height: 44px; - background-size: cover; - background-repeat: no-repeat; - border-radius: 10px; - border: 2px solid #f3efeb; - box-sizing: border-box; - background-color: var(--bg-color-tertiary); -} - -/* create project modal animation */ - -.create-project-form { - position: absolute; - opacity: 1; - transition: opacity 0.3s, transform 0.4s; - width: 420px; -} - -.create-project-form.project-created { - /* background-color: blue; */ - opacity: 0; - -webkit-transform: translateX(-300px); - transform: translateX(-300px); -} - -/* project created modal animation */ - -/* default is to be hidden */ - -.project-created-modal { - /* position: absolute; */ - opacity: 0; - -webkit-transform: scale(0.8); - transform: scale(0.8); - transition: opacity 0.3s, transform 0.4s; -} - -/* then it shows */ - -.project-created-modal.project-created { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); -} -.project-created-modal.project-created .project-modal-content-spacer { - margin-bottom: 7rem; - margin-top: 3rem; + .create-project-form { + + .create-project-image-row { + display: flex; + flex-direction: row; + align-items: flex-end; + + .validating-form-input { + margin-bottom: 0; + } + + .create-project-image { + margin-left: 8px; + width: 44px; + height: 44px; + background-size: cover; + background-repeat: no-repeat; + border-radius: 10px; + border: 2px solid #f3efeb; + box-sizing: border-box; + background-color: var(--bg-color-tertiary); + } + } + } } diff --git a/web/src/components/CreateProjectModal/CreateProjectModal.js b/web/src/components/CreateProjectModal/CreateProjectModal.tsx similarity index 69% rename from web/src/components/CreateProjectModal/CreateProjectModal.js rename to web/src/components/CreateProjectModal/CreateProjectModal.tsx index 954ea1d8..96604e7c 100644 --- a/web/src/components/CreateProjectModal/CreateProjectModal.js +++ b/web/src/components/CreateProjectModal/CreateProjectModal.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useContext } from 'react' import './CreateProjectModal.scss' import ValidatingFormInput from '../ValidatingFormInput/ValidatingFormInput' @@ -8,22 +8,14 @@ import { ProjectModalContent, ProjectModalContentSpacer, ProjectModalHeading, - ProjectModalSubHeading, } from '../ProjectModal/ProjectModal' -import ProjectSecret from '../ProjectSecret/ProjectSecret' import ButtonWithPendingState from '../ButtonWithPendingState/ButtonWithPendingState' - -// since this is a big wordset, dynamically import it -// instead of including in the main bundle -async function generatePassphrase() { - const { default: randomWord } = await import('diceware-word') - return `${randomWord()} ${randomWord()} ${randomWord()} ${randomWord()} ${randomWord()}` -} +import { generatePassphrase } from '../../secrets' +import ToastContext, { ShowToast } from '../../context/ToastContext' function CreateProjectForm({ creatingProject, onSubmit, - projectCreated, projectName, setProjectName, projectCoverUrl, @@ -56,18 +48,6 @@ function CreateProjectForm({ validateProjectName() }, [projectName, shouldInvalidateProjectName]) - useEffect(() => { - // if (projectCoverUrl.length > 0) { - // setisValidProjectCoverUrl(true) - // } else { - // setisValidProjectCoverUrl(false) - // setErrorProjectCoverUrl('Project name is not... ?') - // } - }, [projectCoverUrl]) - - const subheading = - 'You can share the project with people or just keep it to yourself' - const actionButtonContent = ( +
- {/* project name */} @@ -133,55 +108,18 @@ function CreateProjectForm({
) } - -function ProjectCreatedModal({ onDone, projectCreated, projectSecret }) { - return ( -
- - - - - - - -
- ) -} - export default function CreateProjectModal({ showModal, onClose, onCreateProject, }) { - const reset = () => { - setProjectName('') - setProjectCoverUrl('') - } - const [creatingProject, setCreatingProject] = useState(false) - - const onSubmit = async () => { - setCreatingProject(true) - try { - await onCreateProject( - { - name: projectName, - image: projectCoverUrl, - }, - projectSecret - ) - setCreatingProject(false) - setProjectCreated(true) - reset() - } catch (e) { - console.log(e) - } - } + // pull in the toast context + const { setToastState } = useContext(ToastContext) - let hasUnmounted = false + const [creatingProject, setCreatingProject] = useState(false) + const [projectSecret, setProjectSecret] = useState('') + const [projectName, setProjectName] = useState('') + const [projectCoverUrl, setProjectCoverUrl] = useState('') const genAndSetPassphrase = async () => { try { @@ -192,16 +130,7 @@ export default function CreateProjectModal({ } } - const onDone = () => { - onClose() - setProjectCreated(false) - genAndSetPassphrase() - } - - const [projectSecret, setProjectSecret] = useState('') - const [projectCreated, setProjectCreated] = useState(false) - const [projectName, setProjectName] = useState('') - const [projectCoverUrl, setProjectCoverUrl] = useState('') + let hasUnmounted = false // generate a passphrase on component mount useEffect(() => { @@ -212,22 +141,56 @@ export default function CreateProjectModal({ } }, []) + const resetCreateProjectState = () => { + onClose() + // wait for the closing of the modal animation + setTimeout(() => { + setCreatingProject(false) + setProjectName('') + setProjectCoverUrl('') + genAndSetPassphrase() + }, 100) + } + + const onSubmit = async () => { + setCreatingProject(true) + try { + await onCreateProject( + { + name: projectName, + image: projectCoverUrl, + }, + projectSecret + ) + resetCreateProjectState() + // if project creation is successful + setToastState({ + id: ShowToast.Yes, + text: 'New project created', + type: 'confirmation', + }) + } catch (e) { + // if project creation is not successful + console.log(e) + resetCreateProjectState() + setToastState({ + id: ShowToast.Yes, + text: 'Error creating project', + type: 'destructive', + }) + } + } + return ( - = ({ - Are you sure you want to delete the project '{projectName}'? This + Are you sure you want to delete the project '{projectName}'? This action will delete all the project data and can’t be undone. diff --git a/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.component.tsx b/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.component.tsx index e0c34b8d..40ca13a1 100644 --- a/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.component.tsx +++ b/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.component.tsx @@ -34,6 +34,15 @@ import Icon from '../../../../Icon/Icon' export type EvDetailsOwnProps = { projectId: CellIdString outcome: ComputedOutcome + content: string + githubInputLinkText: string + description: string + setContent: React.Dispatch> + setGithubInputLinkText: React.Dispatch> + setDescription: React.Dispatch> + updateOutcome: (outcome: Outcome, actionHash: ActionHashB64) => Promise + updateOutcomeWithLatest: () => Promise + cleanOutcome: () => Outcome } export type EvDetailsConnectorStateProps = { @@ -64,7 +73,6 @@ export type EvDetailsConnectorDispatchProps = { text: string, backgroundColor: string ) => Promise - updateOutcome: (outcome: Outcome, actionHash: ActionHashB64) => Promise createOutcomeMember: ( outcomeActionHash: ActionHashB64, memberAgentPubKey: AgentPubKeyB64, @@ -85,6 +93,14 @@ const EvDetails: React.FC = ({ // own props projectId, outcome, + content, + githubInputLinkText, + description, + setContent, + setGithubInputLinkText, + setDescription, + updateOutcomeWithLatest, + cleanOutcome, // state props activeAgentPubKey, projectTags, @@ -113,31 +129,17 @@ const EvDetails: React.FC = ({ } }, [outcomeActionHash]) - const cleanOutcome = (): Outcome => { - return { - ...outcome, - editorAgentPubKey: activeAgentPubKey, - timestampUpdated: moment().unix(), - content, - description, - githubLink: githubInputLinkText, - } - } - const updateOutcomeWithLatest = async () => { - await updateOutcome(cleanOutcome(), outcomeActionHash) - } - /* Title */ - // the live editor state - const [content, setContent] = useState('') + // handle change (or update) of outcome const outcomeContent = outcome ? outcome.content : '' useEffect(() => { setContent(outcomeContent) }, [outcomeContent]) const onTitleBlur = () => { + console.log('onTitleBlur triggered') updateOutcomeWithLatest() endTitleEdit(outcomeActionHash) } @@ -159,8 +161,7 @@ const EvDetails: React.FC = ({ /* Github Link */ - // the live github link editor state - const [githubInputLinkText, setGithubInputLinkText] = useState('') + const [isEditingGithubLink, setIsEditingGithubLink] = useState(false) const outcomeGithubLink = outcome ? outcome.githubLink : '' useEffect(() => { @@ -256,8 +257,7 @@ const EvDetails: React.FC = ({ /* Description */ - // the live editor state - const [description, setDescription] = useState('') + // the latest persisted state const outcomeDescription = outcome ? outcome.description : '' // sync the live editor state with the diff --git a/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.connector.tsx b/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.connector.tsx index 79137e8f..05cf7974 100644 --- a/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.connector.tsx +++ b/web/src/components/ExpandedViewMode/EVMiddleColumn/TabContent/EvDetails/EvDetails.connector.tsx @@ -145,15 +145,6 @@ function mapDispatchToProps( }) return dispatch(updateTag(cellIdString, updatedExistingTag)) }, - updateOutcome: async (outcome: Outcome, actionHash: ActionHashB64) => { - const appWebsocket = await getAppWs() - const projectsZomeApi = new ProjectsZomeApi(appWebsocket) - const updatedOutcome = await projectsZomeApi.outcome.update(cellId, { - actionHash, - entry: outcome, - }) - return dispatch(updateOutcome(cellIdString, updatedOutcome)) - }, createOutcomeMember: async ( outcomeActionHash: ActionHashB64, memberAgentPubKey: AgentPubKeyB64, diff --git a/web/src/components/ExpandedViewMode/EVRightColumn/EVRightColumn.scss b/web/src/components/ExpandedViewMode/EVRightColumn/EVRightColumn.scss index 79a284d9..aea78a77 100644 --- a/web/src/components/ExpandedViewMode/EVRightColumn/EVRightColumn.scss +++ b/web/src/components/ExpandedViewMode/EVRightColumn/EVRightColumn.scss @@ -9,8 +9,9 @@ flex-direction: column; justify-content: space-between; overflow: scroll; - padding: 2.5rem 3rem 0 2rem; - width: 11.25rem; + padding: 2.5rem 3rem 0 1.75rem; + box-sizing: border-box; + width: 16.25rem; } .ev-right-column-section { diff --git a/web/src/components/ExpandedViewMode/ExpandedViewMode.component.tsx b/web/src/components/ExpandedViewMode/ExpandedViewMode.component.tsx index 108527fe..825ff6e7 100644 --- a/web/src/components/ExpandedViewMode/ExpandedViewMode.component.tsx +++ b/web/src/components/ExpandedViewMode/ExpandedViewMode.component.tsx @@ -3,13 +3,12 @@ import { CSSTransition } from 'react-transition-group' import EVMiddleColumn from './EVMiddleColumn/EVMiddleColumn' import EVLeftColumn from './EVLeftColumn/EVLeftColumn' import { ExpandedViewTab } from './NavEnum' -import { CellIdString, ActionHashB64 } from '../../types/shared' -import { ComputedOutcome, ComputedScope } from '../../types' +import { CellIdString, ActionHashB64, AgentPubKeyB64 } from '../../types/shared' +import { ComputedOutcome, Outcome } from '../../types' import './ExpandedViewMode.scss' import ButtonClose from '../ButtonClose/ButtonClose' import Breadcrumbs from '../Breadcrumbs/Breadcrumbs' import hashCodeId from '../../api/clientSideIdHash' -import OnClickOutside from '../OnClickOutside/OnClickOutside' // props passed to the component by the parent export type ExpandedViewModeOwnProps = { @@ -23,12 +22,14 @@ export type ExpandedViewModeOwnProps = { childrenList: React.ReactElement taskList: React.ReactElement rightColumn: React.ReactElement + updateOutcome: (outcome: Outcome, actionHash: ActionHashB64) => Promise } // redux props export type ExpandedViewModeConnectorProps = { outcomeActionHash: ActionHashB64 commentCount: number + activeAgentPubKey: AgentPubKeyB64 } export type ExpandedViewModeProps = ExpandedViewModeOwnProps & diff --git a/web/src/components/ExpandedViewMode/ExpandedViewMode.connector.tsx b/web/src/components/ExpandedViewMode/ExpandedViewMode.connector.tsx index a19568fe..f3f3ec29 100644 --- a/web/src/components/ExpandedViewMode/ExpandedViewMode.connector.tsx +++ b/web/src/components/ExpandedViewMode/ExpandedViewMode.connector.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import { connect } from 'react-redux' import { RootState } from '../../redux/reducer' import ConnectedEVRightColumn from './EVRightColumn/EVRightColumn.connector' @@ -42,6 +42,7 @@ function mapStateToProps( return { outcomeActionHash, commentCount: comments.length, + activeAgentPubKey: state.agentAddress, } } @@ -75,6 +76,50 @@ const ConnectedExpandedViewMode: React.FC = ({ updateOutcome, createOutcomeWithConnection, }) => { + // the live editor state + const [content, setContent] = useState('') + // the live github link editor state + const [githubInputLinkText, setGithubInputLinkText] = useState('') + // the live editor state + const [description, setDescription] = useState('') + + // close Expanded view after hitting Esc key: + useEffect(() => { + const onKeyDown = async (event) => { + // if we are on Map View + // we should let Map View handle the Escape key + if (event.key === 'Escape') { + const updateTo = localCleanOutcome() + await updateOutcome(updateTo, outcome.actionHash) + + onClose() + } + } + if (outcome) { + document.body.addEventListener('keyup', onKeyDown) + } + // for teardown, unbind event listeners + return () => { + if (outcome) { + document.body.removeEventListener('keyup', onKeyDown) + } + } + }, [outcome, content, description, githubInputLinkText, activeAgentPubKey]) + + const localCleanOutcome = (): Outcome => { + return { + ...outcome, + editorAgentPubKey: activeAgentPubKey, + timestampUpdated: moment().unix(), + content, + description, + githubLink: githubInputLinkText, + } + } + const updateOutcomeWithLatest = async () => { + await updateOutcome(localCleanOutcome(), outcome.actionHash) + } + const updateOutcomeTaskList = (taskList: Array) => { const cleanedOutcome = cleanOutcome(outcome) return updateOutcome( @@ -163,7 +208,23 @@ const ConnectedExpandedViewMode: React.FC = ({ } // redux connected expanded view components - const details = + const details = ( + + ) const comments = ( = ({ onClose={onClose} outcome={outcome} outcomeAndAncestors={outcomeAndAncestors} + updateOutcome={updateOutcome} /> ) } diff --git a/web/src/components/Header/HeaderLeftPanel.tsx b/web/src/components/Header/HeaderLeftPanel.tsx index 7f57eaa4..94d3b30d 100644 --- a/web/src/components/Header/HeaderLeftPanel.tsx +++ b/web/src/components/Header/HeaderLeftPanel.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react' +import React, { useContext, useEffect, useRef, useState } from 'react' import { NavLink, Route, useLocation, useRouteMatch } from 'react-router-dom' import useOnClickOutside from 'use-onclickoutside' @@ -23,6 +23,9 @@ import triangleTopWhite from '../../images/triangle-top-white.svg' // @ts-ignore import DoorOpen from '../../images/door-open.svg' import { ModalState, OpenModal } from '../../context/ModalContexts' +import { getCurrentDateFormatted } from '../../utils' +import useFileDownloaded from '../../hooks/useFileDownloaded' +import ToastContext, { ShowToast } from '../../context/ToastContext' function ActiveEntryPoint({ entryPoint, @@ -98,6 +101,19 @@ const HeaderLeftPanel: React.FC = ({ : [] const ref = useRef() + const { setToastState } = useContext(ToastContext) + const { fileDownloaded, setFileDownloaded } = useFileDownloaded() + + useEffect(() => { + if (fileDownloaded) { + setFileDownloaded(false) + setToastState({ + id: ShowToast.Yes, + text: 'Project Exported', + type: 'confirmation', + }) + } + }, [fileDownloaded, setFileDownloaded, setToastState]) // map, table and priority view routes @@ -111,6 +127,14 @@ const HeaderLeftPanel: React.FC = ({ useOnClickOutside(ref, () => setOpenEntryPointPicker(false)) const [openEntryPointPicker, setOpenEntryPointPicker] = useState(false) + // replace spaces with dashes for project name + // add in the export date like 2023-12-31 and make it based on the + // timezone of the user + const projectNameForExport = `${projectName.replace( + /\s/g, + '-' + )}-${getCurrentDateFormatted()}` + return (
@@ -212,10 +236,12 @@ const HeaderLeftPanel: React.FC = ({ withTooltip tooltipText="Project Settings" size="header" - onClick={() => setModalState({ - id: OpenModal.ProjectSettings, - cellId: projectId - })} + onClick={() => + setModalState({ + id: OpenModal.ProjectSettings, + cellId: projectId, + }) + } className="header-action-icon" /> {/* Export */} @@ -240,24 +266,16 @@ const HeaderLeftPanel: React.FC = ({ { - setModalState({ - id: OpenModal.ProjectExported, - projectName, - }) setIsExportOpen(false) }} /> { - setModalState({ - id: OpenModal.ProjectExported, - projectName, - }) setIsExportOpen(false) }} /> @@ -274,7 +292,7 @@ const HeaderLeftPanel: React.FC = ({ onClickInviteMember={() => { setModalState({ id: OpenModal.InviteMembers, - passphrase: projectPassphrase + passphrase: projectPassphrase, }) }} /> diff --git a/web/src/components/ImportProjectModal/ImportProjectModal.tsx b/web/src/components/ImportProjectModal/ImportProjectModal.tsx index bdc09a89..7c3e550e 100644 --- a/web/src/components/ImportProjectModal/ImportProjectModal.tsx +++ b/web/src/components/ImportProjectModal/ImportProjectModal.tsx @@ -16,6 +16,7 @@ import { CellIdString } from '../../types/shared' import { installProject } from '../../projects/installProject' import { CellId } from '@holochain/client' import { BackwardsCompatibleProjectExportSchema } from 'zod-models' +import { generatePassphrase } from '../../secrets' function ImportProjectFilePicker({ showModal, onFilePicked, onCancel }) { const [fileFormatInvalidMessage, setFileFormatInvalidMessage] = useState( @@ -172,11 +173,6 @@ const ImportProjectModal: React.FC = ({ const [outcomeCount, setOutcomeCount] = useState(0) const onFilePicked = async (projectData: object) => { - // if (!projectData.tags) { - // alert('Cannot import projects from versions prior to v1.0.0-alpha') - // onClose() - // return - // } let projectIds: { cellIdString: CellIdString cellId: CellId @@ -186,16 +182,22 @@ const ImportProjectModal: React.FC = ({ let projectDataParsed = BackwardsCompatibleProjectExportSchema.parse( projectData ) - console.log(projectDataParsed) - const passphrase = projectDataParsed.projectMeta.passphrase + const newRandomPassphrase = await generatePassphrase() + const newProjectData = { + ...projectDataParsed, + projectMeta: { + ...projectDataParsed.projectMeta, + passphrase: newRandomPassphrase, + }, + } setImportingProject(true) - setProjectName(projectDataParsed.projectMeta.name) - setOutcomeCount(Object.keys(projectDataParsed.outcomes).length) + setProjectName(newProjectData.projectMeta.name) + setOutcomeCount(Object.keys(newProjectData.outcomes).length) // first step is to install the app and DNA - projectIds = await installProject(passphrase) + projectIds = await installProject(newRandomPassphrase) // keep the existing passphrase, such that other users know how // to enter the new project - await onImportProject(projectIds.cellIdString, projectData, passphrase) + await onImportProject(projectIds.cellIdString, projectData, newRandomPassphrase) setImportingProject(false) setProjectImported(true) } catch (e) { diff --git a/web/src/components/IntroScreen/IntroScreen.scss b/web/src/components/IntroScreen/IntroScreen.scss index 30d44e73..5d651faf 100644 --- a/web/src/components/IntroScreen/IntroScreen.scss +++ b/web/src/components/IntroScreen/IntroScreen.scss @@ -3,34 +3,12 @@ display: flex; flex-direction: column; - // .skip-intro-button { - // position: absolute; - // right: 0; - // cursor: pointer; - // font-size: 0.9rem; - // font-family: var(--font-family-primary-medium); - // padding: 1.125rem; - // color: #717171; - // transition: color 0.2s; - // z-index: 99; - // box-sizing: border-box; - // height: 3rem; - - // &:hover { - // color: #5f65ff; - // } - // } - .intro-screen-content-frame { overflow: hidden; margin: auto auto; width: 100vw; position: relative; - // @media (max-width: 1188px) { - // width: 48rem; - // } - .button-back { top: 41.2%; left: 0; diff --git a/web/src/components/InviteMembersModal/InviteMembersModal.scss b/web/src/components/InviteMembersModal/InviteMembersModal.scss index e69de29b..e8ed65e5 100644 --- a/web/src/components/InviteMembersModal/InviteMembersModal.scss +++ b/web/src/components/InviteMembersModal/InviteMembersModal.scss @@ -0,0 +1,3 @@ +.invite-members-modal { + width: 33rem; +} \ No newline at end of file diff --git a/web/src/components/InviteMembersModal/InviteMembersModal.js b/web/src/components/InviteMembersModal/InviteMembersModal.tsx similarity index 75% rename from web/src/components/InviteMembersModal/InviteMembersModal.js rename to web/src/components/InviteMembersModal/InviteMembersModal.tsx index 323c54d7..b9e3d288 100644 --- a/web/src/components/InviteMembersModal/InviteMembersModal.js +++ b/web/src/components/InviteMembersModal/InviteMembersModal.tsx @@ -7,28 +7,26 @@ import { ProjectModalContent, ProjectModalContentSpacer, ProjectModalHeading, + ProjectModalSubHeading, } from '../ProjectModal/ProjectModal' import ProjectSecret from '../ProjectSecret/ProjectSecret' export default function InviteMembersModal({ showModal, onClose, passphrase }) { - const onDone = () => { - onClose() - } - return ( + - + ) } diff --git a/web/src/components/JoinProjectModal/JoinProjectModal.scss b/web/src/components/JoinProjectModal/JoinProjectModal.scss index f3c36190..639a52a8 100644 --- a/web/src/components/JoinProjectModal/JoinProjectModal.scss +++ b/web/src/components/JoinProjectModal/JoinProjectModal.scss @@ -1,59 +1,5 @@ -.join-project-modal-wrapper { - overflow: hidden; +.join-project-modal { + width: 31.5rem; position: relative; justify-content: space-between; - width: 34.5rem; - height: 27rem; -} - -.project-modal-button .button.large { - width: 170px; -} - -/* join project modal animation */ - -.join-project-form { - position: absolute; - opacity: 1; - transition: opacity 0.3s, transform 0.4s; - width: 420px; -} - -.join-project-form.project-join-check-is-done { - /* background-color: blue; */ - opacity: 0; - -webkit-transform: translateX(-300px); - transform: translateX(-300px); - display: flex; - flex-direction: column; - justify-content: space-between; -} - -/* project join follow up modal animation */ - -/* default is to be hidden */ - -.project-join-follow-up { - /* position: absolute; */ - opacity: 0; - -webkit-transform: scale(0.8); - transform: scale(0.8); - transition: opacity 0.3s, transform 0.4s; -} - -/* then it shows */ - -.project-join-follow-up.project-join-check-is-done { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - display: flex; - flex-direction: column; - justify-content: space-between; - height: 420px; -} - -.project-join-follow-up-content-wrapper { - margin-bottom: 40px; - line-height: 1.45; -} +} \ No newline at end of file diff --git a/web/src/components/JoinProjectModal/JoinProjectModal.tsx b/web/src/components/JoinProjectModal/JoinProjectModal.tsx index bf387ec4..dc1d03ef 100644 --- a/web/src/components/JoinProjectModal/JoinProjectModal.tsx +++ b/web/src/components/JoinProjectModal/JoinProjectModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useContext, useState } from 'react' import './JoinProjectModal.scss' import ValidatingFormInput from '../ValidatingFormInput/ValidatingFormInput' @@ -12,14 +12,14 @@ import { } from '../ProjectModal/ProjectModal' import ButtonWithPendingState from '../ButtonWithPendingState/ButtonWithPendingState' import { CellIdString } from '../../types/shared' +import ToastContext, { ShowToast } from '../../context/ToastContext' function JoinProjectForm({ - checkDone, projectSecret, onSecretChange, invalidText, pendingJoiningProject, - onClickDone, + onClickJoinProject, }) { const joinProjectButtonContent = ( +
@@ -46,13 +42,13 @@ function JoinProjectForm({ invalidInput={invalidText} errorText={invalidText} label="Project Invitation Secret" - helpText="Paste the 5 word secret you received from the project host" + helpText="Paste the 5-word secret you received from the project host" /> -
- - -
- -
- If a peer is found, you are likely to be able to immediately begin to - access the project, although a short sync period in the queue may be - required before you can access it. -
-
- -
- ) -} - export default function JoinProjectModal({ showModal, onClose, @@ -95,34 +68,51 @@ export default function JoinProjectModal({ doJoinProject: (projectSecret: string) => Promise joinedProjectsSecrets: string[] }) { - const reset = () => { - setProjectSecret('') - setPendingJoiningProject(false) - setInvalidText('') - setCheckDone(false) - } + // pull in the toast context + const { setToastState } = useContext(ToastContext) const [checkDone, setCheckDone] = useState(false) const [projectSecret, setProjectSecret] = useState('') const [invalidText, setInvalidText] = useState('') const [pendingJoiningProject, setPendingJoiningProject] = useState(false) - const onClickDone = async () => { + const resetJoinProjectState = () => { + onClose() + // wait for the closing of the modal animation + setTimeout(() => { + setProjectSecret('') + setPendingJoiningProject(false) + setInvalidText('') + setCheckDone(false) + }, 100) + } + + const onClickJoinProject = async () => { setPendingJoiningProject(true) try { await doJoinProject(projectSecret) setCheckDone(true) setPendingJoiningProject(false) + resetJoinProjectState() + // if project joining is successful + setToastState({ + id: ShowToast.Yes, + text: 'Project has been queued for syncing', + type: 'confirmation', + }) } catch (e) { + // if project joining is not successful console.log(e) + resetJoinProjectState() + setToastState({ + id: ShowToast.Yes, + text: 'Error joining the project', + type: 'destructive', + }) // TODO: add better detail here - setInvalidText('There was an error while joining project: ' + e.message) + // setInvalidText('There was an error while joining project: ' + e.message) } } - const onDone = () => { - reset() - onClose() - } const onSecretChange = (userInputText: string) => { setInvalidText('') @@ -144,17 +134,15 @@ export default function JoinProjectModal({ - ) diff --git a/web/src/components/LoadingScreen/LoadingScreen.scss b/web/src/components/LoadingScreen/LoadingScreen.scss index e7c1efd0..0ef8f037 100644 --- a/web/src/components/LoadingScreen/LoadingScreen.scss +++ b/web/src/components/LoadingScreen/LoadingScreen.scss @@ -6,7 +6,7 @@ position: fixed; top: 0; background-color: var(--bg-color-primary); - z-index: 10000; + z-index: 8; } .loading_screen { diff --git a/web/src/components/MapViewContextMenu/MapViewContextMenu.tsx b/web/src/components/MapViewContextMenu/MapViewContextMenu.tsx index c6c34b3e..9455267f 100644 --- a/web/src/components/MapViewContextMenu/MapViewContextMenu.tsx +++ b/web/src/components/MapViewContextMenu/MapViewContextMenu.tsx @@ -1,8 +1,9 @@ -import React from 'react' +import React, { useContext } from 'react' import { ActionHashB64, CellIdString } from '../../types/shared' import './MapViewContextMenu.scss' import ContextMenu from '../ContextMenu/ContextMenu' +import ToastContext, { ShowToast } from '../../context/ToastContext' export type CheckboxProps = { projectCellId: CellIdString @@ -36,6 +37,10 @@ const Checkbox: React.FC = ({ collapseOutcome, unsetContextMenu, }) => { + +// pull in the toast context +const { setToastState } = useContext(ToastContext) + const wrappedCollapseOutcome = () => { collapseOutcome(projectCellId, outcomeActionHash) unsetContextMenu() @@ -47,6 +52,11 @@ const Checkbox: React.FC = ({ const copyOutcomeStatement = () => { navigator.clipboard.writeText(outcomeStatement) unsetContextMenu() + setToastState({ + id: ShowToast.Yes, + text: 'Outcome statement copied to clipboard', + type: 'confirmation', + }) } const actions = [] diff --git a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.component.tsx b/web/src/components/MapViewCreateOutcome/MapViewCreateOutcome.component.tsx similarity index 52% rename from web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.component.tsx rename to web/src/components/MapViewCreateOutcome/MapViewCreateOutcome.component.tsx index c190643e..61b508c8 100644 --- a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.component.tsx +++ b/web/src/components/MapViewCreateOutcome/MapViewCreateOutcome.component.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react' +import React, { useEffect, useRef, useState } from 'react' import useOnClickOutside from 'use-onclickoutside' import TextareaAutosize from 'react-textarea-autosize' import moment from 'moment' @@ -11,7 +11,7 @@ import { lineHeightMultiplier, secondZoomThreshold, } from '../../drawing/dimensions' -import './MapViewOutcomeTitleForm.scss' +import './MapViewCreateOutcome.scss' import { AgentPubKeyB64, CellIdString, @@ -19,14 +19,22 @@ import { Option, } from '../../types/shared' import { LinkedOutcomeDetails, Outcome, RelationInput } from '../../types' +import Checkbox from '../Checkbox/Checkbox' +import ButtonCheckbox from '../ButtonCheckbox/ButtonCheckbox' +import { coordsCanvasToPage } from '../../drawing/coordinateSystems' +import Icon from '../Icon/Icon' -export type MapViewOutcomeTitleFormOwnProps = { +export type MapViewCreateOutcomeOwnProps = { projectId: CellIdString } -export type MapViewOutcomeTitleFormConnectorStateProps = { +export type MapViewCreateOutcomeConnectorStateProps = { activeAgentPubKey: AgentPubKeyB64 scale: number + translate: { + x: number + y: number + } // the value of the text input content: string // coordinates in css terms for the box @@ -38,7 +46,7 @@ export type MapViewOutcomeTitleFormConnectorStateProps = { existingParentConnectionAddress: ActionHashB64 } -export type MapViewOutcomeTitleFormConnectorDispatchProps = { +export type MapViewCreateOutcomeConnectorDispatchProps = { // callbacks updateContent: (content: string) => void deleteConnection: (actionHash: ActionHashB64) => Promise @@ -49,13 +57,14 @@ export type MapViewOutcomeTitleFormConnectorDispatchProps = { closeOutcomeForm: () => void } -export type MapViewOutcomeTitleFormProps = MapViewOutcomeTitleFormOwnProps & - MapViewOutcomeTitleFormConnectorStateProps & - MapViewOutcomeTitleFormConnectorDispatchProps +export type MapViewCreateOutcomeProps = MapViewCreateOutcomeOwnProps & + MapViewCreateOutcomeConnectorStateProps & + MapViewCreateOutcomeConnectorDispatchProps -const MapViewOutcomeTitleForm: React.FC = ({ +const MapViewCreateOutcome: React.FC = ({ activeAgentPubKey, scale, + translate, content, maybeLinkedOutcome, existingParentConnectionAddress, @@ -66,40 +75,53 @@ const MapViewOutcomeTitleForm: React.FC = ({ createOutcomeWithConnection, closeOutcomeForm, }) => { + const [isSmallScopeChecked, setIsSmallScopeChecked] = useState(false) + const [textIsFocused, setTextIsFocused] = useState(false) + /* EVENT HANDLERS */ // when the text value changes const handleChange = (event) => { updateContent(event.target.value) } - // when a key is pressed - const handleKeyDown = (event) => { - if (event.key === 'Enter') { - event.preventDefault() - handleSubmit() + + // keyboard listener + useEffect(() => { + // since Enter will be used to toggle the checkbox + // when that is focused, we can submit on pure Enter for the textbox + // but they must use Command/Ctrl-Enter to submit when the button/checkbox + // is focused + const handleKeyDown = (e: KeyboardEvent) => { + if (textIsFocused && e.key === 'Enter') { + handleSubmit() + } else if (!textIsFocused && e.key === 'Enter' && e.metaKey) { + handleSubmit() + } } - } + document.addEventListener('keydown', handleKeyDown) + return () => { + document.removeEventListener('keydown', handleKeyDown) + } + }, [ + textIsFocused, + content, + activeAgentPubKey, + isSmallScopeChecked, + existingParentConnectionAddress, + ]) + // when the input comes into focus const handleFocus = (event) => { // select the text event.target.select() + setTextIsFocused(true) } - // when the input leaves focus (not focused on editing title) - const handleBlur = (event) => { - // if creating an Outcome - event.preventDefault() - handleSubmit() + const handleBlur = () => { + setTextIsFocused(false) } // this can get called via keyboard events - // or via 'onClickOutside' of the MapViewOutcomeTitleForm component - const handleSubmit = async (event?) => { - if (event) { - // this is to prevent the page from refreshing - // when the form is submitted, which is the - // default behaviour - event.preventDefault() - } - + // or via 'onClickOutside' of the MapViewCreateOutcome component + const handleSubmit = async () => { // do not allow submit with no content if (!content || content === '') { closeOutcomeForm() @@ -132,9 +154,21 @@ const MapViewOutcomeTitleForm: React.FC = ({ // defaults timestampUpdated: null, editorAgentPubKey: null, - scope: { - Uncertain: { smallsEstimate: 0, timeFrame: null, inBreakdown: false }, - }, + scope: isSmallScopeChecked + ? { + Small: { + achievementStatus: 'NotAchieved', + targetDate: null, + taskList: [], + }, + } + : { + Uncertain: { + smallsEstimate: 0, + timeFrame: null, + inBreakdown: false, + }, + }, tags: [], description: '', isImported: false, @@ -145,48 +179,50 @@ const MapViewOutcomeTitleForm: React.FC = ({ ) } - // the default - let fontSizeToUse = fontSize - if (scale < secondZoomThreshold) { - fontSizeToUse = fontSizeExtraLarge - } else if (scale < firstZoomThreshold) { - fontSizeToUse = fontSizeLarge - } - const textStyle = { - fontSize: fontSizeToUse, - lineHeight: `${parseInt(fontSizeToUse) * lineHeightMultiplier}px`, - } - const ref = useRef() useOnClickOutside(ref, handleSubmit) + const pageCoords = coordsCanvasToPage( + { + x: leftConnectionXPosition, + y: topConnectionYPosition, + }, + translate, + scale + ) + return (
-
+
- +
+ {/* small scope option checkbox */} +
+ setIsSmallScopeChecked(newState)} + icon={} + text={'This Outcome is Small Scope'} + /> +
) } -export default MapViewOutcomeTitleForm +export default MapViewCreateOutcome diff --git a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.connector.ts b/web/src/components/MapViewCreateOutcome/MapViewCreateOutcome.connector.ts similarity index 89% rename from web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.connector.ts rename to web/src/components/MapViewCreateOutcome/MapViewCreateOutcome.connector.ts index a4fcbb55..c5bc9dfb 100644 --- a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.connector.ts +++ b/web/src/components/MapViewCreateOutcome/MapViewCreateOutcome.connector.ts @@ -8,7 +8,7 @@ import { closeOutcomeForm, updateContent, } from '../../redux/ephemeral/outcome-form/actions' -import MapViewOutcomeTitleForm, { MapViewOutcomeTitleFormConnectorDispatchProps, MapViewOutcomeTitleFormConnectorStateProps, MapViewOutcomeTitleFormOwnProps } from './MapViewOutcomeTitleForm.component' +import MapViewCreateOutcome, { MapViewCreateOutcomeConnectorDispatchProps, MapViewCreateOutcomeConnectorStateProps, MapViewCreateOutcomeOwnProps } from './MapViewCreateOutcome.component' import ProjectsZomeApi from '../../api/projectsApi' import { getAppWs } from '../../hcWebsockets' import { cellIdFromString } from '../../utils' @@ -23,10 +23,10 @@ import { LAYOUT_ANIMATION_TYPICAL_MS } from '../../constants' // to pass it to a component for rendering, as specific properties that // that component expects -function mapStateToProps(state: RootState): MapViewOutcomeTitleFormConnectorStateProps { +function mapStateToProps(state: RootState): MapViewCreateOutcomeConnectorStateProps { const { ui: { - viewport: { scale }, + viewport: { scale, translate }, // all the state for this component is store under state->ui->outcomeForm outcomeForm: { content, @@ -42,6 +42,7 @@ function mapStateToProps(state: RootState): MapViewOutcomeTitleFormConnectorStat maybeLinkedOutcome, activeAgentPubKey: state.agentAddress, scale, + translate, existingParentConnectionAddress, content, leftConnectionXPosition: leftConnectionXPosition, @@ -53,7 +54,7 @@ function mapStateToProps(state: RootState): MapViewOutcomeTitleFormConnectorStat // Designed to pass functions into components which are already wrapped as // action dispatchers for redux action types -function mapDispatchToProps(dispatch, ownProps: MapViewOutcomeTitleFormOwnProps): MapViewOutcomeTitleFormConnectorDispatchProps { +function mapDispatchToProps(dispatch, ownProps: MapViewCreateOutcomeOwnProps): MapViewCreateOutcomeConnectorDispatchProps { const { projectId: cellIdString } = ownProps const cellId = cellIdFromString(cellIdString) return { @@ -105,4 +106,4 @@ function mapDispatchToProps(dispatch, ownProps: MapViewOutcomeTitleFormOwnProps) export default connect( mapStateToProps, mapDispatchToProps -)(MapViewOutcomeTitleForm) \ No newline at end of file +)(MapViewCreateOutcome) \ No newline at end of file diff --git a/web/src/components/MapViewCreateOutcome/MapViewCreateOutcome.scss b/web/src/components/MapViewCreateOutcome/MapViewCreateOutcome.scss new file mode 100644 index 00000000..c2a779e5 --- /dev/null +++ b/web/src/components/MapViewCreateOutcome/MapViewCreateOutcome.scss @@ -0,0 +1,45 @@ +.map-view-outcome-statement-card { + position: absolute; + display: flex; + flex-direction: column; + justify-content: left; + background-color: var(--bg-color-secondary); + border-radius: 1rem; + box-shadow: 0 0 36px var(--color-brown-shadow); + padding: 3rem 2.75rem 2.5rem 2.75rem; + width: 24rem; + box-sizing: border-box; + + .map-view-outcome-statement-form { + margin-bottom: 2.5rem; + // margin: 4rem 4rem 1.5rem 4rem; + + textarea { + background-color: transparent; + border: 0; + outline: none; + padding: 0; + width: 18rem; + height: 4.3rem; + resize: none; + color: var(--text-color-primary); + font-family: var(--font-family-primary-semibold); + font-size: 18px; + + &::selection { + // TODO: decide on this + background-color: var(--bg-color-light-green); + } + } + } + + .map-view-outcome-statement-checkbox { + width: 100%; + + + .button-checkbox-wrapper { + width: fit-content; + font-family: var(--font-family-primary-medium); + } + } +} diff --git a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.scss b/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.scss deleted file mode 100644 index 1bb2be98..00000000 --- a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.scss +++ /dev/null @@ -1,26 +0,0 @@ -.map-view-outcome-title-form-wrapper { - position: absolute; - display: flex; -} - -.map-view-outcome-title-form-form { - width: 25rem; -} - -.map-view-outcome-title-form-form textarea { - background-color: transparent; - border: 0; - outline: none; - padding: 0; - margin: 4.65rem 4rem 1.5rem 4rem; - width: 24.5rem; - height: 4.3rem; - resize: none; - color: var(--text-color-primary); - font-family: var(--font-family-primary-bold); -} - -.map-view-outcome-title-form-form ::selection { - // TODO: decide on this - // background: var(--bg-color-achieved); -} diff --git a/web/src/components/MigrationProgress/MigrationProgress.scss b/web/src/components/MigrationProgress/MigrationProgress.scss index 52fd2e35..20ee8e0a 100644 --- a/web/src/components/MigrationProgress/MigrationProgress.scss +++ b/web/src/components/MigrationProgress/MigrationProgress.scss @@ -40,11 +40,8 @@ .run-update-rotating-logo { width: 3.625rem; height: 3.625rem; - // line-height: 110px; - // margin-top: 10px; - // margin-left: 10px; position: absolute; - z-index: 100; + z-index: 6; transition: 0.2s linear transform; img { diff --git a/web/src/components/Modal/Modal.scss b/web/src/components/Modal/Modal.scss index f9f5a3f2..99e0d3b6 100644 --- a/web/src/components/Modal/Modal.scss +++ b/web/src/components/Modal/Modal.scss @@ -8,7 +8,7 @@ display: flex; justify-content: center; align-items: center; - z-index: 9999; + z-index: 7; .modal-content-wrapper { display: flex; @@ -54,7 +54,6 @@ position: absolute; width: 32.5rem; box-sizing: border-box; - padding: 4rem; background-color: #ffff; border-radius: 1rem; box-shadow: 0px 0px 1rem var(--shadow-color); @@ -62,8 +61,15 @@ flex-direction: column; justify-content: space-between; transition: 0.2s all; - overflow-y: scroll; max-height: calc(100vh - 2rem); + overflow-y: hidden; + + .modal-scrollable-content { + padding: 4rem 4rem 3rem 4rem; + box-sizing: border-box; + width: 100%; + overflow-y: auto; // change to auto so the scrollbar only appears when necessary + } .modal-button-close { position: absolute; diff --git a/web/src/components/Modal/Modal.tsx b/web/src/components/Modal/Modal.tsx index 5b34b24c..f7684591 100644 --- a/web/src/components/Modal/Modal.tsx +++ b/web/src/components/Modal/Modal.tsx @@ -115,7 +115,9 @@ const Modal: React.FC = ({
)} +
{children} +
{/* */}
diff --git a/web/src/components/MultiEditBar/MultiEditBar.scss b/web/src/components/MultiEditBar/MultiEditBar.scss index 04496efa..601e0b49 100644 --- a/web/src/components/MultiEditBar/MultiEditBar.scss +++ b/web/src/components/MultiEditBar/MultiEditBar.scss @@ -2,17 +2,19 @@ .multi-edit-bar { position: fixed; - bottom: -100px; - left: 50%; - transform: translateX(-50%); - padding: 0 8px; + left: -100px; + top: 50%; + transform: translateY(-50%); + padding: 0.5rem; background-color: #ffffff; box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.16); border-radius: 10px; opacity: 1; display: flex; + flex-direction: column; align-items: center; - height: 50px; + justify-content: center; + width: 50px; transition: bottom 0.2s ease-in-out; } @@ -22,7 +24,7 @@ } .multi-edit-bar.has-selection { - bottom: 10px; + left: 10px; } .multi-edit-bar > .icon { diff --git a/web/src/components/NetworkInfo/NetworkInfo.tsx b/web/src/components/NetworkInfo/NetworkInfo.tsx index 58a9cb44..c0e578b0 100644 --- a/web/src/components/NetworkInfo/NetworkInfo.tsx +++ b/web/src/components/NetworkInfo/NetworkInfo.tsx @@ -6,14 +6,33 @@ import { VersionInfo } from '../../hooks/useVersionChecker' export type NetworkInfoProps = { versionInfo: VersionInfo + wasmLogs: string[] + holochainLogs: { + info: object[] + warn: object[] + error: object[] + debug: object[] + unknown: string[] + } + errors: string[] } -const NetworkInfo: React.FC = ( - { - versionInfo - } -) => { +enum LogsToShow { + Info = 'Info', + Warn = 'Warn', + Debug = 'Debug', + Error = 'Error', + Unknown = 'Unknown', +} + +const NetworkInfo: React.FC = ({ + versionInfo, + holochainLogs, + wasmLogs, + errors, +}) => { const [networkStats, setNetworkStats] = useState({}) + const [logsToShow, setLogsToShow] = useState(LogsToShow.Info) useEffect(() => { const getNetworkStats = async () => { @@ -28,6 +47,9 @@ const NetworkInfo: React.FC = ( return () => clearInterval(interval) }, []) + const logs = + holochainLogs[logsToShow.toLowerCase() as keyof typeof holochainLogs] + return (
@@ -46,6 +68,44 @@ const NetworkInfo: React.FC = ( updates every 5 seconds
+ +

Errors

+ {errors.map((error) => ( +

{error}

+ ))} + +

Wasm Logs

+ + {wasmLogs.map((log) => ( +

{log}

+ ))} +

Holochain Logs

+ + + + + +

{logsToShow} Logs

+ {logs.map((log: string | object, index: number) => { + return ( +

+ {typeof log === 'object' + ? log['fields']['message'] || + log['fields']['msg'] || + log['fields']['err'] + : log} + {'\n'}({typeof log === 'object' ? log['module_path'] : ''}) +

+ ) + })}
) } diff --git a/web/src/components/PickerTemplate/PickerTemplate.scss b/web/src/components/PickerTemplate/PickerTemplate.scss index c5d6f1cd..a0ffba3e 100644 --- a/web/src/components/PickerTemplate/PickerTemplate.scss +++ b/web/src/components/PickerTemplate/PickerTemplate.scss @@ -6,7 +6,7 @@ padding: 20px 0px; // margin: 0 1rem; top: 0; - z-index: 10; + z-index: 5; width: 20rem; .button-close-wrapper { diff --git a/web/src/components/PreferenceSelect/PreferenceSelect.scss b/web/src/components/PreferenceSelect/PreferenceSelect.scss index 11479175..504f89f9 100644 --- a/web/src/components/PreferenceSelect/PreferenceSelect.scss +++ b/web/src/components/PreferenceSelect/PreferenceSelect.scss @@ -1,7 +1,7 @@ /* PreferenceSelect */ .preference-select { - padding: 20px 0; + padding: 1.5rem 0; /* border-bottom: 1px solid #e3e3e3; */ } @@ -39,7 +39,6 @@ .preference-select-options-wrapper { display: flex; flex-direction: row; - padding: 0 30px; margin-bottom: 1rem; } @@ -51,12 +50,15 @@ text-align: center; border: 1px solid #e3e3e3; border-radius: 10px; - width: 150px; + width: 180px; height: 120px; - margin: 0 auto; display: block; cursor: pointer; transition: border 0.2s ease-in-out; + + &:first-child { + margin-right: 1rem; + } } .preference-select-option:hover, @@ -66,7 +68,7 @@ } .preference-select-option-content { - width: 150px; + width: 180px; height: 120px; display: table-cell; vertical-align: middle; diff --git a/web/src/components/PreferenceSelect/PreferenceSelect.tsx b/web/src/components/PreferenceSelect/PreferenceSelect.tsx index ad4c4e3d..b0078987 100644 --- a/web/src/components/PreferenceSelect/PreferenceSelect.tsx +++ b/web/src/components/PreferenceSelect/PreferenceSelect.tsx @@ -1,6 +1,7 @@ import React from 'react' import './PreferenceSelect.scss' import Icon from '../Icon/Icon' +import Typography from '../Typography/Typography' function PreferenceSelectExtra({ children, @@ -61,9 +62,13 @@ function PreferenceSelect({ // @ts-ignore )} */} -
{title}
+
+ {title} +
+ +
+ {subtitle}
-
{subtitle}
{options}
{descriptions} diff --git a/web/src/components/Preferences/Preferences.scss b/web/src/components/Preferences/Preferences.scss index c7d60786..0268111b 100644 --- a/web/src/components/Preferences/Preferences.scss +++ b/web/src/components/Preferences/Preferences.scss @@ -1,22 +1,102 @@ -.preferences-title { - font-size: 24px; - line-height: 32px; - color: #4d4d4d; - font-family: 'gilroyextrabold', 'gilroyextrabold', Helvetica, sans-serif; - text-align: center; - margin-bottom: 20px; +.preferences-modal { } -.navigation-mode-option-icon-trackpad .icon { - height: 49px; - width: 47px; - margin-bottom: -8px; +// Preferences Modal animation +.preferences-modal-enter { + opacity: 0; } -.navigation-mode-option-icon-mouse .icon { - height: 45px; - width: 40px; - margin-bottom: -5px; +.preferences-modal-enter-active { + opacity: 1; + transition: 0.2s all; +} + +.preferences-modal-exit { + opacity: 1; +} + +.preferences-modal-exit-active { + opacity: 0; + transition: 0.2s all; +} + +.preferences { + background-color: var(--bg-color-secondary); + width: calc(100vw); + height: calc(100vh); + overflow-y: hidden; + box-sizing: border-box; + position: absolute; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 4; + + .preferences-close { + position: absolute; + right: 2.5rem; + top: 2.5rem; + } + + .preferences-save-button { + border-top: 1.5px solid var(--border-color-timberwolf); + width: 100%; + text-align: center; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 1rem 0 2rem 0; + } + + .preferences-inner-wrapper { + overflow-y: hidden; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .preferences-scrollable { + overflow-y: auto; + flex-grow: 1; + } + + .preferences-content { + max-width: 30rem; + display: flex; + flex-direction: column; + align-items: center; + box-sizing: border-box; + padding: 1rem 3rem; + } + } + + .preferences-title { + font-size: 24px; + line-height: 1.5; + font-family: var(--font-family-primary-bold); + text-align: center; + border-bottom: 1.5px solid var(--border-color-timberwolf); + width: 100%; + padding: 2.75rem 0 2rem; + } +} + +.navigation-mode-option-icon-trackpad { + .icon { + height: 49px; + width: 47px; + margin-bottom: -8px; + } +} + +.navigation-mode-option-icon-mouse { + .icon { + height: 45px; + width: 40px; + margin-bottom: -5px; + } } .navigation-mode-description-title-wrapper { @@ -24,12 +104,12 @@ flex-direction: row; align-items: center; margin-bottom: 10px; -} -.navigation-mode-description-title-wrapper .icon { - height: 12px; - cursor: unset; - padding: 0; + .icon { + height: 12px; + cursor: unset; + padding: 0; + } } .navigation-mode-description-title { @@ -46,12 +126,3 @@ color: #3b3b3b; margin-left: 26px; } - -.preferences-save-button { - text-align: center; - margin-top: 40px; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} diff --git a/web/src/components/Preferences/Preferences.tsx b/web/src/components/Preferences/Preferences.tsx index 097fd9fe..4cf1838a 100644 --- a/web/src/components/Preferences/Preferences.tsx +++ b/web/src/components/Preferences/Preferences.tsx @@ -1,9 +1,8 @@ -import React, { useState } from 'react' +import React, { useContext, useState } from 'react' import './Preferences.scss' import Icon from '../Icon/Icon' import Button from '../Button/Button' -import Modal from '../Modal/Modal' import { COORDINATES, KeyboardNavigationPreference, @@ -17,6 +16,9 @@ import PreferenceSelect, { PreferenceSelectOption, } from '../PreferenceSelect/PreferenceSelect' import { ModalState, OpenModal } from '../../context/ModalContexts' +import ButtonClose from '../ButtonClose/ButtonClose' +import { CSSTransition } from 'react-transition-group' +import ToastContext, { ShowToast } from '../../context/ToastContext' function Descriptions({ navigation }) { return ( @@ -83,7 +85,7 @@ function NavigationModeInternal({ navigation, setNavigationSelected }) {
- setKeyboardNavigationSelected(MODAL)} - iconName="trackpad.svg" - iconExtraClassName="navigation-mode-option-icon-trackpad" - title="Use a Modal" - /> setKeyboardNavigationSelected(COORDINATES)} - iconName="mouse.svg" + iconName="arrow-down-left.svg" iconExtraClassName="navigation-mode-option-icon-mouse" - title="Use Coordinates" + title="The most left" + /> + setKeyboardNavigationSelected(MODAL)} + iconName="aspect-ratio.svg" + iconExtraClassName="navigation-mode-option-icon-trackpad" + title="Show a modal" /> ) @@ -121,7 +123,7 @@ function KeyboardNavigationModeInternal({
{ setNavigationPreference(navigationSelected) setKeyboardNavigationPreference(keyboardNavigationSelected) setModalState({ id: OpenModal.None }) + setToastState({ + id: ShowToast.Yes, + text: 'Preferences saved', + type: 'confirmation', + }) } const close = () => { // reset navigation selected to @@ -170,24 +182,35 @@ export default function Preferences({ } return ( - -
Preferences
- - - -
-
- + ) } diff --git a/web/src/components/ProfileEditForm/ProfileEditForm.scss b/web/src/components/ProfileEditForm/ProfileEditForm.scss index 92296838..2926f3f3 100644 --- a/web/src/components/ProfileEditForm/ProfileEditForm.scss +++ b/web/src/components/ProfileEditForm/ProfileEditForm.scss @@ -1,8 +1,6 @@ .profile_edit_form { text-align: center; - margin: 0 auto; - overflow-y: scroll; - height: 100%; + overflow-y: auto; .small-close { position: absolute; diff --git a/web/src/components/ProjectSecret/ProjectSecret.scss b/web/src/components/ProjectSecret/ProjectSecret.scss index fa848c48..2e9b7605 100644 --- a/web/src/components/ProjectSecret/ProjectSecret.scss +++ b/web/src/components/ProjectSecret/ProjectSecret.scss @@ -2,50 +2,42 @@ display: flex; flex-direction: column; position: relative; -} - -.project-secret-row { - display: flex; - flex-direction: row; - align-items: flex-end; - position: relative; -} - -.project-secret-row .validating-form-input { - margin-bottom: 0; -} - -.project-secret-copy-secret { - margin-left: 8px; - border: 0.125rem solid var(--border-color-form-input); - border-radius: 10px; - padding: 7px; - cursor: pointer; - transition: border 0.2s ease-in-out; - box-sizing: border-box; -} - -.project-secret-copy-secret:hover { - border: 0.125rem solid var(--text-color-link); -} - -.project-secret-copy-secret .inner-icon { - transition: background-color 0.2s ease-in-out; -} - -.project-secret-copy-secret:hover .inner-icon { - background-color: var(--text-color-link); -} - -.project-secret-copy-secret .icon { - padding-bottom: 0; - margin: 0 auto; -} -.secret-copy-message { - color: var(--color-forest-green); - font-size: 14px; - position: absolute; - bottom: -1.375rem; - left: 0.25rem; + .project-secret-row { + display: flex; + flex-direction: row; + align-items: flex-end; + position: relative; + + .validating-form-input { + margin-bottom: 0; + } + + .project-secret-copy-secret { + margin-left: 0.5rem; + border: 0.125rem solid var(--border-color-form-input); + border-radius: 0.625rem; + padding: 0.5rem; + cursor: pointer; + transition: border 0.2s ease-in-out; + box-sizing: border-box; + + &:hover { + border: 0.125rem solid var(--text-color-link); + + .inner-icon { + background-color: var(--text-color-link); + } + } + + .icon { + padding-bottom: 0; + margin: 0 auto; + } + + .inner-icon { + transition: background-color 0.2s ease-in-out; + } + } + } } diff --git a/web/src/components/ProjectSecret/ProjectSecret.js b/web/src/components/ProjectSecret/ProjectSecret.tsx similarity index 64% rename from web/src/components/ProjectSecret/ProjectSecret.js rename to web/src/components/ProjectSecret/ProjectSecret.tsx index 476cd952..394d7a28 100644 --- a/web/src/components/ProjectSecret/ProjectSecret.js +++ b/web/src/components/ProjectSecret/ProjectSecret.tsx @@ -1,27 +1,29 @@ -import React, { useState } from 'react' +import React, { useContext} from 'react' import './ProjectSecret.scss' import Icon from '../Icon/Icon' import ValidatingFormInput from '../ValidatingFormInput/ValidatingFormInput' +import ToastContext, { ShowToast } from '../../context/ToastContext' export default function ProjectSecret({ passphrase }) { - // Copied to clipboard message validation for project invitation secret - - const copyMessage = 'Secret copied to clipboard' - - const [showCopyMessage, setShowCopyMessage] = useState(false) + // pull in the toast context + const { setToastState } = useContext(ToastContext) const copySecretToClipboard = () => { navigator.clipboard.writeText(passphrase) - setShowCopyMessage(true) + setToastState({ + id: ShowToast.Yes, + text: 'Project secret copied to clipboard', + type: 'confirmation', + }) } return (
+ {/* @ts-ignore */} {/* copy to clipboard button */}
- {showCopyMessage && ( -
{copyMessage}
- )}
) diff --git a/web/src/components/ProjectSettingsModal/ProjectSettingsModal.component.tsx b/web/src/components/ProjectSettingsModal/ProjectSettingsModal.component.tsx index b6f351f9..5c251a25 100644 --- a/web/src/components/ProjectSettingsModal/ProjectSettingsModal.component.tsx +++ b/web/src/components/ProjectSettingsModal/ProjectSettingsModal.component.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useContext } from 'react' import ValidatingFormInput from '../ValidatingFormInput/ValidatingFormInput' import Modal from '../Modal/Modal' import { @@ -15,6 +15,7 @@ import { ModalState, OpenModal } from '../../context/ModalContexts' import { passphraseToUid } from '../../secrets' import { PROJECT_APP_PREFIX } from '../../holochainConfig' import { getAllApps } from '../../projectAppIds' +import ToastContext, { ShowToast } from '../../context/ToastContext' function ProjectDeleteButton({ onClick, @@ -94,7 +95,8 @@ function EditProjectForm({ // } }, [projectCoverUrl]) - // const subheading = `Any changes will apply for all team members.` + // pull in the toast context + const { setToastState } = useContext(ToastContext) // validate before firing event const submit = () => { @@ -102,6 +104,11 @@ function EditProjectForm({ setShouldInvalidateProjectName(true) if (projectName.length > 0 && !updatingProject) { onSubmit() + setToastState({ + id: ShowToast.Yes, + text: 'Project settings saved', + type: 'confirmation', + }) } } @@ -199,6 +206,7 @@ export default function ProjectSettingsModal({ setModalState, memberCount, }: ProjectSettingsModalProps) { + const [cachedProjectMetaHash, setCachedProjectMetaHash] = useState('') const [updatingProject, setUpdatingProject] = useState(false) const [projectName, setProjectName] = useState('') const [projectCoverUrl, setProjectCoverUrl] = useState('') @@ -217,18 +225,28 @@ export default function ProjectSettingsModal({ cellIdString ) setUpdatingProject(false) + setCachedProjectMetaHash('') + onClose() + } + + const onCancel = () => { + setCachedProjectMetaHash('') onClose() } // editable useEffect(() => { - if (project) { + if (project && project.actionHash !== cachedProjectMetaHash) { + setCachedProjectMetaHash(project.actionHash) setProjectName(project.name) setProjectCoverUrl(project.image) setProjectPassphrase(project.passphrase) } - }, [project]) + }, [project, setCachedProjectMetaHash]) + // TODO: once we migrate off version 9 stream, we can simplify this + // this commented out code, because we won't have any projects that use + // the old pattern which had randomness in it. // const uid = passphraseToUid(projectPassphrase) // const installedAppId = `${PROJECT_APP_PREFIX}-${uid}` useEffect(() => { @@ -246,7 +264,7 @@ export default function ProjectSettingsModal({ ) } + diff --git a/web/src/components/RemoveSelfProjectModal/RemoveSelfProjectModal.tsx b/web/src/components/RemoveSelfProjectModal/RemoveSelfProjectModal.tsx index 6dd6615e..863f6da8 100644 --- a/web/src/components/RemoveSelfProjectModal/RemoveSelfProjectModal.tsx +++ b/web/src/components/RemoveSelfProjectModal/RemoveSelfProjectModal.tsx @@ -52,7 +52,7 @@ const RemoveSelfProjectModal: React.FC = ({ Are you sure you want to remove yourself from the shared project ' - {projectName}'? If you remove yourself from this project you won’t + {projectName}'? If you remove yourself from this project you won’t have access to it anymore.

You can join this project again later, as long as it still has diff --git a/web/src/components/Toast/Toast.scss b/web/src/components/Toast/Toast.scss index 7062c129..ccfcff48 100644 --- a/web/src/components/Toast/Toast.scss +++ b/web/src/components/Toast/Toast.scss @@ -1,5 +1,5 @@ .toast { - z-index: 2; + z-index: 9; width: fit-content; position: absolute; bottom: -3rem; @@ -13,6 +13,21 @@ font-family: var(--font-family-primary-medium); transition: 0.5s bottom ease; + .toast-icon { + cursor: default; + } + + .toast-text { + margin-right: 0.25rem; + margin-left: 0.25rem; + margin-bottom: 0.05rem; + cursor: default; + } + + .toast-close { + margin-top: 0.125rem; + } + &.visible { bottom: 1.25rem; } @@ -20,20 +35,36 @@ &.confirmation { color: var(--text-color-toast-confirmation); background-color: var(--bg-color-toast-confirmation); + + .icon.toast-icon .inner-icon { + background-color: var(--text-color-toast-confirmation); + } } &.information { color: var(--text-color-toast-information); background-color: var(--bg-color-toast-information); + + .icon.toast-icon .inner-icon { + background-color: var(--text-color-toast-information); + } } &.warning { color: var(--text-color-toast-warning); background-color: var(--bg-color-toast-warning); + + .icon.toast-icon .inner-icon { + background-color: var(--text-color-toast-warning); + } } &.destructive { color: var(--text-color-toast-destructive); background-color: var(--bg-color-toast-destructive); + + .icon.toast-icon .inner-icon { + background-color: var(--text-color-toast-destructive); + } } } diff --git a/web/src/components/Toast/Toast.tsx b/web/src/components/Toast/Toast.tsx index 8ecc7087..e41c46d2 100644 --- a/web/src/components/Toast/Toast.tsx +++ b/web/src/components/Toast/Toast.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react' import './Toast.scss' import ButtonClose from '../ButtonClose/ButtonClose' import { ShowToast, ToastState } from '../../context/ToastContext' +import Icon from '../Icon/Icon' export type ToastProps = { toastState: ToastState @@ -13,26 +14,35 @@ const Toast: React.FC = ({ toastState, setToastState }) => { // create a local cached state of whatever the most // recent text and type given is, so that we can make a nice // disappearing transition - const { recentText, recentType } = useToastStateCache(toastState, setToastState) + const { recentText, recentType } = useToastStateCache( + toastState, + setToastState + ) return (

- {recentText} + +
{recentText}
+
setToastState({ id: ShowToast.No })} /> +
) } export default Toast -const TOAST_TIMEOUT_DELAY = 3000 +const TOAST_TIMEOUT_DELAY = 4000 // 2 purposes: // 1. cache the most recent text and type of toast // 2. set a timeout-driven delay to clear the toast -function useToastStateCache(toastState: ToastState, setToastState: React.Dispatch>) { +function useToastStateCache( + toastState: ToastState, + setToastState: React.Dispatch> +) { const [recentText, setRecentText] = useState('') const [recentType, setRecentType] = useState('') const intervalRef = useRef(undefined) diff --git a/web/src/components/Tooltip/Tooltip.scss b/web/src/components/Tooltip/Tooltip.scss index f1057fd1..827f8e6f 100644 --- a/web/src/components/Tooltip/Tooltip.scss +++ b/web/src/components/Tooltip/Tooltip.scss @@ -8,7 +8,7 @@ font-family: var(--font-family-primary-bold); display: flex; flex-direction: column; - z-index: 9999; + z-index: 7; // this is the default &.with-transition { diff --git a/web/src/components/Typography/Typography.scss b/web/src/components/Typography/Typography.scss index b68b1f5e..f0ddd881 100644 --- a/web/src/components/Typography/Typography.scss +++ b/web/src/components/Typography/Typography.scss @@ -3,20 +3,20 @@ color: var(--text-color-primary); // Headings &.h1 { - font: 2rem var(--font-family-secondary-extrabold); + font: 2rem var(--font-family-primary-bold); line-height: 1.45; } &.h2 { - font: 1.75rem var(--font-family-secondary-extrabold); + font: 1.75rem var(--font-family-primary-bold); line-height: 1.45; } &.h3 { - font: 1.5rem var(--font-family-secondary-extrabold); + font: 1.5rem var(--font-family-primary-bold); line-height: 1.35; letter-spacing: 0.01rem; } &.h4 { - font: 1.375rem var(--font-family-secondary-extrabold); + font: 1.375rem var(--font-family-primary-bold); line-height: 1.45; } &.h5 { @@ -59,7 +59,7 @@ } } &.subtitle-modal { - font: 1rem var(--font-family-primary-bold); + font: 1rem var(--font-family-primary-medium); color: var(--text-color-tertiary); line-height: 1.45; } @@ -143,4 +143,11 @@ color: var(--text-color-primary); line-height: 1.6; } + + &.label-help { + font-family: var(--font-family-primary-medium); + color: var(--text-color-tertiary); + font-size: max(0.875rem, 11.5px); + line-height: 1.6; + } } diff --git a/web/src/components/Typography/Typography.tsx b/web/src/components/Typography/Typography.tsx index ff19004b..90adcd88 100644 --- a/web/src/components/Typography/Typography.tsx +++ b/web/src/components/Typography/Typography.tsx @@ -26,6 +26,7 @@ export type TypographyProps = { | 'breadcrumbs-bold' | 'button-text' | 'label' + | 'label-help' | 'heading-modal' | 'subtitle-modal' | 'body-modal' diff --git a/web/src/components/ValidatingFormInput/ValidatingFormInput.js b/web/src/components/ValidatingFormInput/ValidatingFormInput.js index 301f1508..11dacf01 100644 --- a/web/src/components/ValidatingFormInput/ValidatingFormInput.js +++ b/web/src/components/ValidatingFormInput/ValidatingFormInput.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types' import './ValidatingFormInput.scss' -import ValidationCheck from '../../images/circle-check.svg' -import ValidationX from '../../images/circle-x.svg' +import ValidationCheck from '../../images/validation-check.svg' +import ValidationX from '../../images/validation-x.svg' import Typography from '../Typography/Typography' function ValidatingFormInput({ diff --git a/web/src/components/ValidatingFormInput/ValidatingFormInput.scss b/web/src/components/ValidatingFormInput/ValidatingFormInput.scss index ef9c1871..f063742a 100644 --- a/web/src/components/ValidatingFormInput/ValidatingFormInput.scss +++ b/web/src/components/ValidatingFormInput/ValidatingFormInput.scss @@ -13,7 +13,7 @@ .help-text { color: var(--text-color-tertiary); - font-size: 0.875rem; + font-size: max(0.825rem, 11.5px); line-height: 1.45; margin: 0.125rem 0 0.375rem 0; flex: 1; diff --git a/web/src/context/ModalContexts.ts b/web/src/context/ModalContexts.ts index bcdf26f7..5447253f 100644 --- a/web/src/context/ModalContexts.ts +++ b/web/src/context/ModalContexts.ts @@ -10,7 +10,6 @@ export enum OpenModal { ProjectSettings, InviteMembers, ProjectMigrated, - ProjectExported, Preferences, ProfileEditForm, } @@ -47,10 +46,6 @@ export type ModalState = id: OpenModal.ProjectMigrated cellId: CellIdString } - | { - id: OpenModal.ProjectExported - projectName: string - } | { id: OpenModal.Preferences } diff --git a/web/src/drawing/drawOutcome/computeArguments/argsForDrawGlow.ts b/web/src/drawing/drawOutcome/computeArguments/argsForDrawGlow.ts index 22eaa62d..dda56932 100644 --- a/web/src/drawing/drawOutcome/computeArguments/argsForDrawGlow.ts +++ b/web/src/drawing/drawOutcome/computeArguments/argsForDrawGlow.ts @@ -1,4 +1,4 @@ -import { TOP_PRIORITY_GLOW_COLOR } from '../../../styles' +import { HIGH_PRIORITY_GLOW_COLOR } from '../../../styles' import { borderWidth, cornerRadius } from '../../dimensions' import drawGlow from '../drawGlow' @@ -36,7 +36,7 @@ export const argsForDrawGlow = ({ height, cornerRadius: glowCornerRadius, useGlow, - glowColor: TOP_PRIORITY_GLOW_COLOR, + glowColor: HIGH_PRIORITY_GLOW_COLOR, glowBlur, ctx, } diff --git a/web/src/drawing/index.ts b/web/src/drawing/index.ts index 4ac9e0a3..0de57d92 100644 --- a/web/src/drawing/index.ts +++ b/web/src/drawing/index.ts @@ -452,7 +452,7 @@ function render( DRAW NEW OUTCOME PLACEHOLDER */ // creating a new Outcome - if (outcomeFormIsOpen) { + if (false && outcomeFormIsOpen) { const isHovered = false const isSelected = false const isEditing = true @@ -461,7 +461,7 @@ function render( drawOutcomeCard({ // draw the Outcome with empty text // since the text is presented in the - // MapViewOutcomeTitleForm + // MapViewCreateOutcome skipStatementRender: true, useLineLimit: false, zoomLevel: zoomLevel, diff --git a/web/src/event-listeners/index.ts b/web/src/event-listeners/index.ts index b4593b9e..2b1409aa 100644 --- a/web/src/event-listeners/index.ts +++ b/web/src/event-listeners/index.ts @@ -307,7 +307,6 @@ export default function setupEventListeners( if (!state.ui.expandedView.isOpen && !state.ui.navigationModal.open) { store.dispatch(unselectAll()) } - store.dispatch(closeExpandedView()) store.dispatch(closeOutcomeForm()) store.dispatch(resetOutcomeConnector()) break diff --git a/web/src/hooks/useFileDownloaded.ts b/web/src/hooks/useFileDownloaded.ts index a0c7f54b..9dac1518 100644 --- a/web/src/hooks/useFileDownloaded.ts +++ b/web/src/hooks/useFileDownloaded.ts @@ -6,7 +6,7 @@ export default function useFileDownloaded() { const subscribeToEvent = async () => { if (window.require) { const { ipcRenderer } = window.require('electron') - ipcRenderer.once('exportDownloaded', () => { + ipcRenderer.on('exportDownloaded', () => { setFileDownloaded(true) }) } diff --git a/web/src/hooks/useHolochainErrorAndLog.ts b/web/src/hooks/useHolochainErrorAndLog.ts new file mode 100644 index 00000000..65766104 --- /dev/null +++ b/web/src/hooks/useHolochainErrorAndLog.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react' + +export default function useHolochainErrorAndLog() { + const [wasmLogs, setWasmLogs] = useState([]) + const [holochainInfo, setHolochainInfo] = useState([]) + const [holochainWarn, setHolochainWarn] = useState([]) + const [holochainDebug, setHolochainDebug] = useState([]) + const [holochainError, setHolochainError] = useState([]) + const [holochainUnknown, setHolochainUnknown] = useState([]) + const [errors, setErrors] = useState([]) + + const subscribeToEvents = async () => { + if (window.require) { + const { ipcRenderer } = window.require('electron') + ipcRenderer.on('wasmLog', (event: object, log: string) => { + setWasmLogs((logs) => [...logs, log]) + }) + ipcRenderer.on('holochainLog', (event: object, log: string) => { + try { + let parsedLog: object = JSON.parse(log) + if (parsedLog['level'] === 'INFO') { + setHolochainInfo((logs) => [...logs, parsedLog]) + } else if (parsedLog['level'] === 'WARN') { + setHolochainWarn((logs) => [...logs, parsedLog]) + } else if (parsedLog['level'] === 'DEBUG') { + setHolochainDebug((logs) => [...logs, parsedLog]) + } else if (parsedLog['level'] === 'ERROR') { + setHolochainError((logs) => [...logs, parsedLog]) + } + } catch (e) { + setHolochainUnknown((logs) => [...logs, log]) + } + }) + ipcRenderer.on('holochainError', (event: object, error: string) => { + setErrors((errors) => [...errors, error]) + }) + } + } + + useEffect(() => { + subscribeToEvents() + }, []) + + return { + wasmLogs, + holochainLogs: { + info: holochainInfo, + warn: holochainWarn, + debug: holochainDebug, + error: holochainError, + unknown: holochainUnknown, + }, + errors, + } +} diff --git a/web/src/hooks/useVersionChecker.ts b/web/src/hooks/useVersionChecker.ts index 23f105e5..49f088d8 100644 --- a/web/src/hooks/useVersionChecker.ts +++ b/web/src/hooks/useVersionChecker.ts @@ -84,7 +84,9 @@ function isVersionOutOfDate( const currentVersionParts = currentVersion.split('.').map(Number) const latestVersionParts = latestVersion.split('.').map(Number) - if (currentVersionParts[0] < latestVersionParts[0]) { + // note, don't propose updates to version 10.x.x + // because that is the version with holochain 0.1.x + if (currentVersionParts[0] < latestVersionParts[0] && latestVersionParts[0] !== 10) { return true } else if (currentVersionParts[0] === latestVersionParts[0]) { if (currentVersionParts[1] < latestVersionParts[1]) { diff --git a/web/src/images/arrow-down-left.svg b/web/src/images/arrow-down-left.svg new file mode 100644 index 00000000..93ee4c0b --- /dev/null +++ b/web/src/images/arrow-down-left.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/web/src/images/arrow-right.svg b/web/src/images/arrow-right.svg index 401820e5..4abcc6a1 100644 --- a/web/src/images/arrow-right.svg +++ b/web/src/images/arrow-right.svg @@ -1 +1,6 @@ - \ No newline at end of file + + + + \ No newline at end of file diff --git a/web/src/images/aspect-ratio.svg b/web/src/images/aspect-ratio.svg new file mode 100644 index 00000000..91f85930 --- /dev/null +++ b/web/src/images/aspect-ratio.svg @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/web/src/images/confirmation.svg b/web/src/images/confirmation.svg new file mode 100644 index 00000000..be5f1e0f --- /dev/null +++ b/web/src/images/confirmation.svg @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/web/src/images/destructive.svg b/web/src/images/destructive.svg new file mode 100644 index 00000000..bbec8b1c --- /dev/null +++ b/web/src/images/destructive.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/web/src/images/information.svg b/web/src/images/information.svg new file mode 100644 index 00000000..08f3f465 --- /dev/null +++ b/web/src/images/information.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/web/src/images/validation-check.svg b/web/src/images/validation-check.svg new file mode 100644 index 00000000..f6b7dfc3 --- /dev/null +++ b/web/src/images/validation-check.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/web/src/images/validation-x.svg b/web/src/images/validation-x.svg new file mode 100644 index 00000000..52843cd7 --- /dev/null +++ b/web/src/images/validation-x.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/web/src/images/warning.svg b/web/src/images/warning.svg new file mode 100644 index 00000000..a07e353a --- /dev/null +++ b/web/src/images/warning.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/web/src/redux/ephemeral/local-preferences/reducer.ts b/web/src/redux/ephemeral/local-preferences/reducer.ts index 95fdb4a7..d9dbe139 100644 --- a/web/src/redux/ephemeral/local-preferences/reducer.ts +++ b/web/src/redux/ephemeral/local-preferences/reducer.ts @@ -25,7 +25,7 @@ const defaultState = { // default to trackpad navigation navigation: getLocalItem(NAV_KEY) || TRACKPAD, // default to modal keyboard navigation - keyboardNavigation: getLocalItem(KEYBOARD_NAV_KEY) || MODAL, + keyboardNavigation: getLocalItem(KEYBOARD_NAV_KEY) || COORDINATES, } export default function (state = defaultState, action) { diff --git a/web/src/routes/App.component.tsx b/web/src/routes/App.component.tsx index e6f539da..22331ab9 100644 --- a/web/src/routes/App.component.tsx +++ b/web/src/routes/App.component.tsx @@ -56,6 +56,7 @@ import { } from '../redux/ephemeral/local-preferences/reducer.js' import { ProjectMetaState } from '../redux/persistent/projects/project-meta/reducer' import { MembersState } from '../redux/persistent/projects/members/reducer' +import useHolochainErrorAndLog from '../hooks/useHolochainErrorAndLog' export type AppStateProps = { projectMetas: ProjectMetaState @@ -154,6 +155,7 @@ const App: React.FC = ({ // const updateVersionInfo = useVersionChecker(true) const finishMigrationChecker = useFinishMigrationChecker() const { fileDownloaded, setFileDownloaded } = useFileDownloaded() + const { wasmLogs, holochainLogs, errors: holochainErrors } = useHolochainErrorAndLog() useEffect(() => { if (fileDownloaded) { @@ -337,7 +339,7 @@ const App: React.FC = ({
- {networkInfoOpen && } + {networkInfoOpen && } ) } diff --git a/web/src/routes/CreateProfilePage/CreateProfilePage.scss b/web/src/routes/CreateProfilePage/CreateProfilePage.scss index b6b52999..5d088eb1 100644 --- a/web/src/routes/CreateProfilePage/CreateProfilePage.scss +++ b/web/src/routes/CreateProfilePage/CreateProfilePage.scss @@ -12,7 +12,7 @@ min-width: 556px; box-sizing: border-box; padding-top: 64px; - overflow-y: hidden; + overflow-y: auto; display: flex; align-items: center; justify-content: center; @@ -20,9 +20,7 @@ .profile_edit_form { margin: 3rem 5rem; width: 100%; - min-height: 100%; max-width: 38rem; - overflow-y: scroll; display: flex; flex-direction: column; align-items: center; diff --git a/web/src/routes/Dashboard/Dashboard.component.tsx b/web/src/routes/Dashboard/Dashboard.component.tsx index 74e35d9a..f5fdc65c 100644 --- a/web/src/routes/Dashboard/Dashboard.component.tsx +++ b/web/src/routes/Dashboard/Dashboard.component.tsx @@ -7,7 +7,6 @@ import DashboardEmptyState from '../../components/DashboardEmptyState/DashboardE import CreateProjectModal from '../../components/CreateProjectModal/CreateProjectModal' import ImportProjectModal from '../../components/ImportProjectModal/ImportProjectModal' import JoinProjectModal from '../../components/JoinProjectModal/JoinProjectModal' -import ProjectSettingsModal from '../../components/ProjectSettingsModal/ProjectSettingsModal.connector' // import new modals here import { @@ -83,7 +82,7 @@ const Dashboard: React.FC = ({ const [showJoinModal, setShowJoinModal] = useState(false) const [showImportModal, setShowImportModal] = useState(false) const [showProjectMigratedModal, setShowProjectMigratedModal] = useState('') - // + // // calling this triggers the fetchProjectMeta for each project const { projectStatusInfos, setProjectStatusInfos } = useProjectStatusInfos( @@ -142,20 +141,7 @@ const Dashboard: React.FC = ({ return ( <>
-
- {/* TODO: decide if we want to have this menu on Dashboard page */} - {/* - - My Projects - - - - Settings - */} -
+
My Projects{' '} @@ -193,7 +179,7 @@ const Dashboard: React.FC = ({
= ({ }} onClickUpdateNow={() => { setShowProjectMigratedModal('') - setModalState({ id: OpenModal.UpdateApp, section: ViewingReleaseNotes.MainMessage }) + setModalState({ + id: OpenModal.UpdateApp, + section: ViewingReleaseNotes.MainMessage, + }) }} /> diff --git a/web/src/routes/Dashboard/Dashboard.scss b/web/src/routes/Dashboard/Dashboard.scss index b622be1c..7af2f68c 100644 --- a/web/src/routes/Dashboard/Dashboard.scss +++ b/web/src/routes/Dashboard/Dashboard.scss @@ -8,40 +8,6 @@ overflow: hidden; } -.dashboard-left-menu-item { - font-size: 14px; - font-family: 'gilroyextrabold', Helvetica, sans-serif; - color: #535353; - margin-bottom: 10px; - padding: 8px; - display: flex; - flex-direction: row; - cursor: pointer; - transition: color 0.1s ease-in, background-color 0.1s ease-in; - width: 110px; - border-radius: 10px; - text-decoration: none; -} - -.dashboard-left-menu-item .icon { - margin-right: 6px; -} - -.dashboard-left-menu-item:hover, -.dashboard-left-menu-item.active { - color: #5f65ff; - background-color: #f3f2f2; -} - -.dashboard-left-menu-item:hover .inner-icon, -.dashboard-left-menu-item.active .inner-icon { - background-color: #5f65ff; -} - -.dashboard-left-menu-item .inner-icon { - transition: background-color 0.1s ease-in; -} - /* dashboard my projects */ .dashboard-my-projects { @@ -50,138 +16,139 @@ max-width: 60rem; display: flex; flex-direction: column; -} -@media only screen and (max-width: 1024px) { - .dashboard-my-projects { - margin-right: 2rem; - margin-left: 2rem; - max-width: 100%; + .my-projects-heading { + margin-bottom: 1.25rem; + padding: 0 1rem; + box-sizing: border-box; } -} - -.my-projects-heading { - margin-bottom: 1.25rem; - padding: 0 1rem; - box-sizing: border-box; -} - -.my-projects-header { - border-bottom: 0.125rem solid var(--border-color-timberwolf); - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding-bottom: 6px; - margin: 0 1rem; -} - -.my-projects-header-buttons { - display: flex; - flex-direction: row; -} - -.my-projects-button { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - border-radius: 30px; - font-size: 14px; - padding: 10px 0; - text-align: center; - cursor: pointer; - margin-right: 40px; - font-family: 'PlusJakartaSans-bold'; - color: var(--text-color-primary); - transition: 0.2s all ease; - - &:hover { - color: var(--text-color-link); - } - - &.invite-members { - width: 140px; - // position: absolute; - // top: -2px; - // right: 0; - padding: 6px 16px 6px 8px; - background-color: #f9f8f6; - border: none; - margin-right: 0; - - &:hover { - background-color: #f5f2ef; + .my-projects-header { + border-bottom: 0.125rem solid var(--border-color-timberwolf); + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding-bottom: 0.25rem; + margin: 0 1rem; + + .my-projects-header-buttons { + display: flex; + flex-direction: row; + + .my-projects-button { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + font-size: 14px; + padding: 0.625rem 0; + text-align: center; + cursor: pointer; + margin-right: 2.5rem; + font-family: var(--font-family-primary-semibold); + color: var(--text-color-primary); + transition: 0.2s all ease; + + &:hover { + color: var(--text-color-link); + } + + &.invite-members { + width: 140px; + padding: 6px 16px 6px 8px; + background-color: #f9f8f6; + border: none; + margin-right: 0; + + &:hover { + background-color: #f5f2ef; + } + } + + .icon { + margin-right: 6px; + } + } } - } - - .icon { - margin-right: 6px; - } -} -.my-projects-sorting { - color: #8e8e8e; - font-size: 0.825rem; - display: flex; - flex-direction: row; - align-items: center; - position: relative; - padding: 0.5rem 0; -} - -.my-projects-sorting-selected { - font-size: 0.825rem; - display: flex; - cursor: pointer; - color: #5d5d5d; - margin-left: 8px; - margin-top: 1px; - - .icon { - margin-left: 6px; - transition: transform 0.2s ease-in; - padding: 0px; - margin-top: 2px; - - &.active { - transform: rotateX(180deg); - -webkit-transform: rotate(180deg); + .my-projects-sorting { + color: var(--text-color-quaternary); + font-size: 0.825rem; + display: flex; + flex-direction: row; + align-items: center; + position: relative; + + .my-projects-sorting-selected { + font-size: 0.825rem; + display: flex; + cursor: pointer; + color: var(--text-color-secondary); + margin-left: 0.5rem; + + .icon { + margin-left: 0.375rem; + transition: transform 0.2s ease; + top: 0.075rem; + + &.active { + transform: rotateX(180deg); + -webkit-transform: rotate(180deg); + } + } + + &:hover { + color: #5f65ff; + + .icon .inner-icon { + background-color: #5f65ff; + } + } + } + + .my-projects-sorting-select { + width: 7.5rem; + position: absolute; + right: 0px; + top: 1.5rem; + background: var(--bg-color-secondary); + padding: 8px 0; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.08); + border-radius: 8px; + z-index: 2; + list-style: none; + + li { + text-align: left; + padding: 0.5rem 1rem; + color: var(--text-color-secondary); + + &:hover { + background-color: var(--bg-color-hover); + cursor: pointer; + } + } + } } } - &:hover { - color: #5f65ff; - - .icon .inner-icon { - background-color: #5f65ff; + .my-projects-content { + position: relative; + overflow-y: auto; + flex: 1; + scrollbar-width: none; + padding: 0 1rem 3rem; + box-sizing: border-box; + width: 100%; + + &::-webkit-scrollbar { + width: 0px; /* For vertical scrollbars */ + height: 0px; /* For horizontal scrollbars */ } } } -.my-projects-sorting-select { - position: absolute; - right: 0px; - top: 18px; - background: #ffffff; - padding: 8px 0; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.08); - border-radius: 8px; - z-index: 2; - list-style: none; -} - -.my-projects-sorting-select li { - padding: 8px 20px; - color: #535353; - - &:hover { - background-color: #f2f2f2; - cursor: pointer; - } -} - -// for sorting +// sorting popup anitmation .my-projects-sorting-select-enter { opacity: 0; } @@ -199,19 +166,3 @@ opacity: 0; transition: opacity 100ms ease-out; } - -/* dont show a physical scroll bar */ - -.my-projects-content::-webkit-scrollbar { - // display: none; -} - -.my-projects-content { - position: relative; - overflow-y: scroll; - flex: 1; - scrollbar-width: none; - padding: 0 1rem; - box-sizing: border-box; - width: 100%; -} diff --git a/web/src/routes/GlobalModals.tsx b/web/src/routes/GlobalModals.tsx index 5523d590..529f5c0c 100644 --- a/web/src/routes/GlobalModals.tsx +++ b/web/src/routes/GlobalModals.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import ReactMarkdown from 'react-markdown' import Modal, { ModalContent } from '../components/Modal/Modal' @@ -74,6 +74,23 @@ const GlobalModals: React.FC = ({ history.push('/dashboard') } + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + e.stopPropagation() + setModalToNone() + } + } + + if (modalState.id !== OpenModal.None) { + window.addEventListener('keydown', handleKeyDown) + } + + return () => { + window.removeEventListener('keydown', handleKeyDown) + } + }, [modalState]) + return ( <> {/* This will only show when 'active' prop is true */} @@ -211,30 +228,6 @@ const GlobalModals: React.FC = ({ } /> - - {/* Export Successful Modal */} - - - You just exported the{' '} - - {modalState.id === OpenModal.ProjectExported && - modalState.projectName} - {' '} - project data. You can use that file to transfer the project to a - different owner, or archive as a backup. - - } - primaryButton="Got it" - primaryButtonAction={setModalToNone} - /> - ) } diff --git a/web/src/routes/ProjectView/MapView/MapView.component.tsx b/web/src/routes/ProjectView/MapView/MapView.component.tsx index 90790885..fc99934e 100644 --- a/web/src/routes/ProjectView/MapView/MapView.component.tsx +++ b/web/src/routes/ProjectView/MapView/MapView.component.tsx @@ -13,7 +13,7 @@ import ProjectEmptyState from '../../../components/ProjectEmptyState/ProjectEmpt import MultiEditBar from '../../../components/MultiEditBar/MultiEditBar.connector' import OutcomeConnectors from '../../../components/OutcomeConnectors/OutcomeConnectors.connector' import CollapsedChildrenPills from '../../../components/CollapsedChildrenPills/CollapsedChildrenPills.connector' -import MapViewOutcomeTitleForm from '../../../components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.connector' +import MapViewCreateOutcome from '../../../components/MapViewCreateOutcome/MapViewCreateOutcome.connector' import Tooltip from '../../../components/Tooltip/Tooltip' import './MapView.scss' @@ -183,9 +183,10 @@ const MapView: React.FC = ({ {/* if the scale is greater than or equal to 60% (or we are creating an Outcome) */} {/* because otherwise the font size gets to small and the text is cut off */} - {outcomeFormIsOpen && }
+ + {outcomeFormIsOpen && } {/* below items inside 'mapview-elements-container' maintain their normal scale */} {/* while positioning themselves absolutely (position: absolute) on the screen */} diff --git a/web/src/routes/ProjectView/ProjectView.component.tsx b/web/src/routes/ProjectView/ProjectView.component.tsx index 8aed0d96..4b36a0ab 100644 --- a/web/src/routes/ProjectView/ProjectView.component.tsx +++ b/web/src/routes/ProjectView/ProjectView.component.tsx @@ -171,22 +171,6 @@ const ProjectViewInner: React.FC = ({ setActiveEntryPoints(entryPointActionHashes) }, [JSON.stringify(entryPointActionHashes)]) - // close Expanded view after hitting Esc key: - useEffect(() => { - const onKeyDown = (event) => { - // if we are on Map View - // we should let Map View handle the Escape key - if (!location.pathname.includes('map') && event.key === 'Escape') { - closeExpandedView() - } - } - document.body.addEventListener('keydown', onKeyDown) - // for teardown, unbind event listeners - return () => { - document.body.removeEventListener('keydown', onKeyDown) - } - }, [location]) - return ( <> diff --git a/web/src/secrets.ts b/web/src/secrets.ts index e530b613..44df7236 100644 --- a/web/src/secrets.ts +++ b/web/src/secrets.ts @@ -1,7 +1,14 @@ +// since this is a big wordset, dynamically import it +// instead of including in the main bundle +async function generatePassphrase() { + const { default: randomWord } = await import('diceware-word') + return `${randomWord()} ${randomWord()} ${randomWord()} ${randomWord()} ${randomWord()}` +} + const passphraseToUid = (passphrase: string) => `uid-${passphrase.split(' ').join('-')}` const uidToPassphrase = (uid: string) => uid.replace('uid-', '').split('-').join(' ') -export { passphraseToUid, uidToPassphrase } +export { generatePassphrase, passphraseToUid, uidToPassphrase } diff --git a/web/src/styles.ts b/web/src/styles.ts index 564b0b80..3de1db0e 100644 --- a/web/src/styles.ts +++ b/web/src/styles.ts @@ -45,7 +45,7 @@ const colors = { // map view colors const SELECTED_COLOR = '#344cff' -const TOP_PRIORITY_GLOW_COLOR = '#334CF8' +const HIGH_PRIORITY_GLOW_COLOR = '#334CF8' // canvas outcome statement const STATEMENT_FONT_COLOR = '#222222' @@ -99,7 +99,7 @@ export { STATEMENT_FONT_COLOR, STATEMENT_PLACEHOLDER_COLOR, SELECTED_COLOR, - TOP_PRIORITY_GLOW_COLOR, + HIGH_PRIORITY_GLOW_COLOR, DEFAULT_OUTCOME_BACKGROUND_COLOR, NOT_ACHIEVED_BACKGROUND_COLOR, ACHIEVED_BACKGROUND_COLOR, diff --git a/web/src/utils.ts b/web/src/utils.ts index 0cc76a0d..b83ac318 100644 --- a/web/src/utils.ts +++ b/web/src/utils.ts @@ -23,3 +23,14 @@ export function cellIdFromString(str: string): CellId { const [dnahashstring, agentpubkeyhashstring] = str.split(CELL_ID_DIVIDER) return [hashFromString(dnahashstring), hashFromString(agentpubkeyhashstring)] } + +export function getCurrentDateFormatted() { + const now = new Date() + + const year = now.getFullYear() + // getMonth() returns a zero-based month, so +1 to get the correct month number + const month = (now.getMonth() + 1).toString().padStart(2, '0') + const day = now.getDate().toString().padStart(2, '0') + + return `${year}-${month}-${day}` +} diff --git a/web/src/variables.scss b/web/src/variables.scss index 3cfa2630..fc54af7d 100644 --- a/web/src/variables.scss +++ b/web/src/variables.scss @@ -6,7 +6,7 @@ // color --color-virtually-transaprent: #ffffff00; // when you need an element but transparent --color-green-shadow: #4a604392; // for achieved outcome cards - --shadow-brown-shadow: #8377699b; // for not achieved outcome cards + --color-brown-shadow: #8377699b; // for not achieved outcome cards --color-silver: #757171; //Silver --color-green: #008414; //Green --color-forest-green: #15841d; //Forset green (used for achieved small) @@ -43,6 +43,9 @@ --bg-color-toast-information: #dbe5e2; --bg-color-toast-warning: #f1e5d8; --bg-color-toast-destructive: #eedad4; + // background color for important text + --bg-color-text-important-information: #e6f5f0; + --bg-color-text-important-warning: #f6ece2d8; // background dark --bg-color-primary-dark: #555555; @@ -56,6 +59,7 @@ --border-color-bright-gray: #efefef; //Bright Gray --border-color-pale-silver: #c3bfba; //Pale Silver (100%) --border-color-form-input: #f3efeb; + --border-color-button-checkbox: #ffffff00; // shadow --shadow-color: #c7beb472; //Pale Silver (40% opacity) diff --git a/web/tsconfig.json b/web/tsconfig.json index bef23a1e..7d908ab9 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -1,10 +1,11 @@ { "compilerOptions": { + "strict": false, "outDir": "./dist/", "checkJs": false, "sourceMap": true, "noImplicitAny": false, - "module": "ES6", + "module": "ES2020", "target": "ES6", "jsx": "react", "allowJs": true, diff --git a/yarn.lock b/yarn.lock index 1b87d8f0..86c451ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1670,10 +1670,10 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== -"@lightningrodlabs/electron-holochain@=0.7.8": - version "0.7.8" - resolved "https://registry.yarnpkg.com/@lightningrodlabs/electron-holochain/-/electron-holochain-0.7.8.tgz#7ef0ade47a720d96180ce847de9269e4f55c5b08" - integrity sha512-rJ3ashN7Qz6NLz/J19+O7rLDUB++4aAkBlSNI5xe9++3pLRs0WGxR8YWwiSO3FX4VsiPeTx+sZn9yLBkfiaXgQ== +"@lightningrodlabs/electron-holochain@=0.7.12": + version "0.7.12" + resolved "https://registry.yarnpkg.com/@lightningrodlabs/electron-holochain/-/electron-holochain-0.7.12.tgz#010bf6837b738b9245800ab61220ca34da93b2a7" + integrity sha512-p1TKr5FYYfFlNgeeT5tCCjDPOJ9hHqUPySj7iv50bOyIe1Ch+Rq5DniJEpV9NYCcPDGfZ1eoua2EUeHML2r/4g== dependencies: request "^2.88.2" split "^1.0.1"