| @@ -79,126 +79,141 @@ | |||
| border: 2px solid $borderColor; | |||
| border-radius: $borderRadius; | |||
| position: relative; | |||
| } | |||
| .cover-photo { | |||
| position: relative; | |||
| height: 350px; | |||
| flex: 0 0 350px; | |||
| margin-bottom: 3px; | |||
| cursor: pointer; | |||
| width: 100%; | |||
| max-width: 100%; | |||
| border-radius: $borderRadius; | |||
| box-shadow: #333 5px 5px 5px; | |||
| overflow: hidden; | |||
| .cover-photo { | |||
| position: relative; | |||
| height: 350px; | |||
| flex: 0 0 350px; | |||
| margin-bottom: 3px; | |||
| cursor: pointer; | |||
| img { | |||
| width: 100%; | |||
| max-width: 100%; | |||
| border-radius: $borderRadius; | |||
| box-shadow: #333 5px 5px 5px; | |||
| overflow: hidden; | |||
| img { | |||
| width: 100%; | |||
| transform: scale(1); | |||
| transform: scale(1); | |||
| } | |||
| &:hover { | |||
| img, | |||
| svg { | |||
| transform: scale(1.05); | |||
| } | |||
| } | |||
| &:hover { | |||
| img, | |||
| svg { | |||
| transform: scale(1.05); | |||
| } | |||
| .file-name { | |||
| position: absolute; | |||
| bottom: 0; | |||
| background: white; | |||
| width: 100%; | |||
| text-align: center; | |||
| font-size: 0.8em; | |||
| font-style: italic; | |||
| line-height: 1.2em; | |||
| padding: 0 20px; | |||
| } | |||
| &.cover-photo--no-img { | |||
| //padding-left: calc(350px / 2 - 37.5px); | |||
| display: flex; | |||
| align-items: center; | |||
| flex-direction: column; | |||
| justify-content: center; | |||
| #page-loader { | |||
| padding: 9px; | |||
| } | |||
| .file-name { | |||
| position: absolute; | |||
| bottom: 0; | |||
| background: white; | |||
| width: 100%; | |||
| text-align: center; | |||
| font-size: 0.8em; | |||
| font-style: italic; | |||
| line-height: 1.2em; | |||
| padding: 0 20px; | |||
| svg.no-photos { | |||
| width: 75px; | |||
| height: 75px; | |||
| color: $accentColor; | |||
| } | |||
| } | |||
| } | |||
| &.cover-photo--no-img { | |||
| //padding-left: calc(350px / 2 - 37.5px); | |||
| display: flex; | |||
| align-items: center; | |||
| flex-direction: column; | |||
| justify-content: center; | |||
| .card-details { | |||
| flex: 1 1 auto; | |||
| margin-left: 25px; | |||
| margin-top: 25px; | |||
| #page-loader { | |||
| padding: 9px; | |||
| } | |||
| @media(max-width: 968px) { | |||
| margin-left: 0; | |||
| width: 100%; | |||
| max-width: 450px; | |||
| } | |||
| svg.no-photos { | |||
| width: 75px; | |||
| height: 75px; | |||
| color: $accentColor; | |||
| } | |||
| } | |||
| &.card-details--flex { | |||
| flex: 0 0 100%; | |||
| display: flex; | |||
| align-items: flex-start; | |||
| justify-content: center; | |||
| } | |||
| .card-details { | |||
| flex: 1 1 auto; | |||
| margin-left: 25px; | |||
| margin-top: 25px; | |||
| .card-details-left { | |||
| flex: 0 0 50%; | |||
| } | |||
| @media(max-width: 968px) { | |||
| margin-left: 0; | |||
| width: 100%; | |||
| max-width: 450px; | |||
| } | |||
| .card-details-right { | |||
| flex: 0 0 50%; | |||
| } | |||
| .input-element { | |||
| margin-bottom: 25px; | |||
| .input-error { | |||
| visibility: hidden; | |||
| position: absolute; | |||
| bottom: -1.4em; | |||
| left: 25px; | |||
| font-size: 11pt; | |||
| color: red; | |||
| font-style: italic; | |||
| } | |||
| .input-element { | |||
| margin-bottom: 25px; | |||
| .input-error { | |||
| visibility: hidden; | |||
| position: absolute; | |||
| bottom: -1.4em; | |||
| left: 25px; | |||
| font-size: 11pt; | |||
| color: red; | |||
| font-style: italic; | |||
| } | |||
| } | |||
| } | |||
| .input-buttons { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| @media(max-width: 968px) { | |||
| flex-direction: column; | |||
| } | |||
| .input-buttons { | |||
| .left-buttons { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| @media(max-width: 968px) { | |||
| flex-direction: column; | |||
| button { | |||
| margin-right: 15px; | |||
| } | |||
| .left-buttons { | |||
| display: flex; | |||
| @media(max-width: 968px) { | |||
| justify-content: space-between; | |||
| margin-bottom: 15px; | |||
| button { | |||
| margin-right: 15px; | |||
| } | |||
| @media(max-width: 968px) { | |||
| justify-content: space-between; | |||
| margin-bottom: 15px; | |||
| button { | |||
| margin-right: 0; | |||
| } | |||
| margin-right: 0; | |||
| } | |||
| } | |||
| } | |||
| .right-buttons { | |||
| display: flex; | |||
| .right-buttons { | |||
| display: flex; | |||
| button { | |||
| margin-left: 15px; | |||
| } | |||
| button { | |||
| margin-left: 15px; | |||
| } | |||
| @media(max-width: 968px) { | |||
| justify-content: space-between; | |||
| margin-bottom: 15px; | |||
| @media(max-width: 968px) { | |||
| justify-content: space-between; | |||
| margin-bottom: 15px; | |||
| button { | |||
| margin-left: 0; | |||
| } | |||
| button { | |||
| margin-left: 0; | |||
| } | |||
| } | |||
| } | |||
| @@ -239,4 +254,57 @@ | |||
| font-size: 1rem; | |||
| margin-bottom: 15px; | |||
| } | |||
| } | |||
| .pos--rel { | |||
| position: relative; | |||
| } | |||
| .pointer { | |||
| cursor: pointer; | |||
| } | |||
| .pad { | |||
| padding: 15px; | |||
| } | |||
| .bold { | |||
| font-weight: bold; | |||
| } | |||
| .border { | |||
| border: 2px solid $borderColor; | |||
| } | |||
| .border--round { | |||
| border-radius: $borderRadius; | |||
| } | |||
| .inline-block { | |||
| display: inline-block; | |||
| } | |||
| .flex { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| .flex--vert { | |||
| flex-direction: column; | |||
| } | |||
| .flex--vert-sm { | |||
| @media(max-width: 768px) { | |||
| flex-direction: column; | |||
| } | |||
| } | |||
| .flex--align-center-sm { | |||
| @media(max-width: 768px) { | |||
| align-items: center; | |||
| } | |||
| } | |||
| .flex--stretch { | |||
| justify-content: space-between; | |||
| } | |||
| .flex--align-start { | |||
| align-items: flex-start; | |||
| } | |||
| @@ -15,10 +15,10 @@ import '@fontsource/roboto/500.css'; | |||
| import '@fontsource/roboto/700.css'; | |||
| import Keycloak from "./services/authentication/keycloak"; | |||
| import PortfolioManager from "./pages/portfolio-manager"; | |||
| import AlbumManager from "./pages/album-manager"; | |||
| import InstanceAlbumsPage from "./pages/instance-albums-page"; | |||
| import AlbumPage from "./pages/album-page"; | |||
| import {InstancesPage} from "./pages/instances-page"; | |||
| import {NoAccessPage} from "./pages/no-access"; | |||
| import {NoAccessPage} from "./pages/no-access-page"; | |||
| import {InstancesProvider} from "./services/instances/instances-context"; | |||
| import {AdminLayout} from "./layout"; | |||
| import {Home} from "@mui/icons-material"; | |||
| @@ -37,8 +37,8 @@ const router = createBrowserRouter( | |||
| <Route path="albums/*" element={<Outlet />} handle={{ | |||
| crumb: (routeData) => <Link to={`/${routeData?.params?.instanceId}/albums`}>albums</Link> | |||
| }}> | |||
| <Route index element={<PortfolioManager/>} /> | |||
| <Route path=":albumId" element={<AlbumManager/>} handle={{ | |||
| <Route index element={<InstanceAlbumsPage/>} /> | |||
| <Route path=":albumId" element={<AlbumPage />} handle={{ | |||
| crumb: (routeData) => <Link to={`/${routeData?.params?.instanceId}/albums/${routeData.params.albumId}`}>{routeData.params.albumId}</Link> | |||
| }}/> | |||
| </Route> | |||
| @@ -7,6 +7,7 @@ export class Instance { | |||
| this.title = instance?.title; | |||
| this.subtitle = instance?.subtitle; | |||
| this.coverPhoto = instance?.coverPhoto ? new Photo(instance.coverPhoto) : null; | |||
| this.homeLayout = instance?.homeLayout; | |||
| this.urls = instance?.urls?.map(u => u) || []; | |||
| } | |||
| } | |||
| @@ -9,10 +9,10 @@ import DragAndDrop from "../components/drag-and-drop/drag-and-drop"; | |||
| import {Album} from "../models/album"; | |||
| import Loader from "../components/Loader"; | |||
| import "./album-manager.scss"; | |||
| import "./album-page.scss"; | |||
| import {useInstances} from "../services/instances/use-instances"; | |||
| function AlbumManager() { | |||
| function AlbumPage() { | |||
| const {state} = useLocation(); | |||
| const {album} = state; // Read values passed on state | |||
| const {selectedInstance} = useInstances(); | |||
| @@ -299,4 +299,4 @@ function AlbumManager() { | |||
| </div>; | |||
| } | |||
| export default AlbumManager; | |||
| export default AlbumPage; | |||
| @@ -13,10 +13,10 @@ import Loader from "../components/Loader"; | |||
| import {Button, TextField} from "@mui/material"; | |||
| import {Album} from "../models/album"; | |||
| import "./portfolio-manager.scss" | |||
| import "./instance-albums-page.scss" | |||
| import {useInstances} from "../services/instances/use-instances"; | |||
| function PortfolioManager() { | |||
| function InstanceAlbumsPage() { | |||
| const navigate = useNavigate(); | |||
| const {selectedInstance} = useInstances(); | |||
| @@ -208,4 +208,4 @@ function PortfolioManager() { | |||
| </div> | |||
| } | |||
| export default PortfolioManager; | |||
| export default InstanceAlbumsPage; | |||
| @@ -6,8 +6,7 @@ import {useAuth} from "../services/authentication/use-auth"; | |||
| import {useInstances} from "../services/instances/use-instances"; | |||
| import {Instance} from "../models/instance"; | |||
| import { | |||
| AddAPhoto, AddCircle, | |||
| ArrowRightAltOutlined, Edit, RemoveCircle | |||
| AddAPhoto, AddCircle, ArrowRightAltOutlined, Edit, RadioButtonChecked, RadioButtonUnchecked, RemoveCircle | |||
| } from "@mui/icons-material"; | |||
| import "./instances-page.scss"; | |||
| @@ -16,12 +15,7 @@ import DragAndDrop from "../components/drag-and-drop/drag-and-drop"; | |||
| export function InstancesPage() { | |||
| const navigate = useNavigate(); | |||
| const { | |||
| instances, | |||
| createInstance, | |||
| deleteInstance, | |||
| updateInstance, | |||
| uploadCoverPhoto, | |||
| instanceApiError | |||
| instances, createInstance, deleteInstance, updateInstance, uploadCoverPhoto, instanceApiError | |||
| } = useInstances(); | |||
| const [instancesToUpdate, setInstancesToUpdate] = useState(null); | |||
| const {userData} = useAuth(); | |||
| @@ -59,9 +53,10 @@ export function InstancesPage() { | |||
| } | |||
| const instanceHasChanges = (idx) => { | |||
| return (instancesToUpdate[idx].title || "") !== (instances[idx]?.title || "") || | |||
| (instancesToUpdate[idx].subtitle || "") !== (instances[idx]?.subtitle || "") || | |||
| JSON.stringify(instancesToUpdate[idx].urls) !== JSON.stringify(instances[idx]?.urls); | |||
| return (instancesToUpdate[idx].title || "") !== (instances[idx]?.title || "") | |||
| || (instancesToUpdate[idx].subtitle || "") !== (instances[idx]?.subtitle || "") | |||
| || JSON.stringify(instancesToUpdate[idx].urls) !== JSON.stringify(instances[idx]?.urls) | |||
| || instancesToUpdate[idx].homeLayout !== instances[idx]?.homeLayout; | |||
| } | |||
| const handleCreateInstance = async () => { | |||
| await createInstance(new Instance({ | |||
| @@ -92,7 +87,20 @@ export function InstancesPage() { | |||
| const handleDrop = async (e, idx) => { | |||
| await handleFiles(e, idx) | |||
| } | |||
| const handleSelectLayout = (idx, layout) => { | |||
| instancesToUpdate[idx].homeLayout = layout; | |||
| setInstancesToUpdate([...instancesToUpdate]); | |||
| } | |||
| const homeLayouts = [{ | |||
| id: 1, | |||
| name: "Layout 1" | |||
| }, { | |||
| id: 2, | |||
| name: "Layout 2" | |||
| }, { | |||
| id: 3, | |||
| name: "Layout 3" | |||
| }] | |||
| return <div id="instance-selector-page"> | |||
| <div id="page-title"> | |||
| <div id="left-part"> | |||
| @@ -120,87 +128,101 @@ export function InstancesPage() { | |||
| </>} | |||
| </div> | |||
| <div id="instance-list"> | |||
| {instancesToUpdate?.map((instance, idx) => | |||
| <div className="card-layout instance" key={instance.instanceId}> | |||
| <div | |||
| className={`${idx} cover-photo ${(!isUploading[idx] && instance.coverPhoto ? "" : "cover-photo--no-img")}`}> | |||
| <DragAndDrop onDrop={(e) => handleDrop(e, idx)} | |||
| onClick={(e) => fileInputRefs.current[idx].click(e)} | |||
| isLoading={isUploading[idx]}> | |||
| {!isUploading[idx] && instance.coverPhoto | |||
| ? <> | |||
| <img src={`${instance.coverPhoto.url}?width=450&height=450&fit=cover`} | |||
| alt={instance.coverPhoto.name}/> | |||
| <div className="btn-edit-photo"><Edit /></div> | |||
| </> | |||
| : <AddAPhoto className="no-photos"/> | |||
| } | |||
| </DragAndDrop> | |||
| <input type="file" id={`photosUpload`} ref={(element) => fileInputRefs.current[idx] = element} | |||
| accept="image/*" | |||
| onChange={(e) => handleFiles(e, idx)}></input> | |||
| </div> | |||
| <div className="card-details"> | |||
| <div className="title input-element"> | |||
| <TextField fullWidth={true} variant="standard" | |||
| value={instance.title} | |||
| placeholder="Title..." | |||
| onChange={(e) => handleInstanceTitleChange(e, idx)} | |||
| /> | |||
| </div> | |||
| <div className="description input-element"> | |||
| <TextField fullWidth={true} variant="standard" | |||
| value={instance.subtitle || ""} | |||
| placeholder="Subtitle..." | |||
| onChange={(e) => handleInstanceSubtitleChange(e, idx)} | |||
| /> | |||
| </div> | |||
| <div className="url input-element"> | |||
| <TextField fullWidth={true} variant="standard" | |||
| value={instance.newInstanceUrl || ""} | |||
| placeholder="https://my-photos.be" | |||
| onChange={(e) => handleNewInstanceUrlChange(e, idx)} | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <InputAdornment position="end"> | |||
| {instancesToUpdate?.map((instance, idx) => <div | |||
| className="pad flex flex--stretch flex--align-start border border--round instance" | |||
| key={instance.instanceId}> | |||
| <div | |||
| className={`${idx} cover-photo ${(!isUploading[idx] && instance.coverPhoto ? "" : "cover-photo--no-img")}`}> | |||
| <DragAndDrop onDrop={(e) => handleDrop(e, idx)} | |||
| onClick={(e) => fileInputRefs.current[idx].click(e)} | |||
| isLoading={isUploading[idx]}> | |||
| {!isUploading[idx] && instance.coverPhoto ? <> | |||
| <img src={`${instance.coverPhoto.url}?width=450&height=450&fit=cover`} | |||
| alt={instance.coverPhoto.name}/> | |||
| <div className="btn-edit-photo"><Edit/></div> | |||
| </> : <AddAPhoto className="no-photos"/>} | |||
| </DragAndDrop> | |||
| <input type="file" id={`photosUpload`} ref={(element) => fileInputRefs.current[idx] = element} | |||
| accept="image/*" | |||
| onChange={(e) => handleFiles(e, idx)}></input> | |||
| </div> | |||
| <div className="card-details"> | |||
| <div className="flex flex--align-start flex--vert-sm flex--align-center-sm"> | |||
| <div className="card-details-left"> | |||
| <div className="title input-element"> | |||
| <TextField fullWidth={true} variant="standard" | |||
| value={instance.title} | |||
| placeholder="Title..." | |||
| onChange={(e) => handleInstanceTitleChange(e, idx)} | |||
| /> | |||
| </div> | |||
| <div className="description input-element"> | |||
| <TextField fullWidth={true} variant="standard" | |||
| value={instance.subtitle || ""} | |||
| placeholder="Subtitle..." | |||
| onChange={(e) => handleInstanceSubtitleChange(e, idx)} | |||
| /> | |||
| </div> | |||
| <div className="url input-element"> | |||
| <TextField fullWidth={true} variant="standard" | |||
| value={instance.newInstanceUrl || ""} | |||
| placeholder="https://my-photos.be" | |||
| onChange={(e) => handleNewInstanceUrlChange(e, idx)} | |||
| InputProps={{ | |||
| endAdornment: (<InputAdornment position="end"> | |||
| <IconButton color="warning" | |||
| onClick={(e) => handleAddNewInstanceUrl(e, idx)}> | |||
| <AddCircle/> | |||
| </IconButton> | |||
| </InputAdornment> | |||
| ) | |||
| }}/> | |||
| <div className="instance-links"> | |||
| {instance.urls.map(url => <div className="instance-link" key={url}> | |||
| <IconButton color="error" onClick={(e) => handleDeleteUrl(e, idx, url)}> | |||
| <RemoveCircle/> | |||
| </IconButton> | |||
| <NavLink to={url}>{url}</NavLink> | |||
| </div>)} | |||
| </InputAdornment>) | |||
| }}/> | |||
| <div className="instance-links"> | |||
| {instance.urls.map(url => <div className="instance-link" key={url}> | |||
| <IconButton color="error" onClick={(e) => handleDeleteUrl(e, idx, url)}> | |||
| <RemoveCircle/> | |||
| </IconButton> | |||
| <NavLink to={url}>{url}</NavLink> | |||
| </div>)} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className="input-buttons"> | |||
| <div className="left-buttons"> | |||
| <Button className="photo-save" variant="contained" color="error" | |||
| onClick={(e) => handleDeleteInstance(e, instance.instanceId)}> | |||
| Delete | |||
| </Button> | |||
| <Button className="photo-save" variant="contained" color="info" | |||
| disabled={!instanceHasChanges(idx)} | |||
| onClick={() => handleSaveInstance(idx)}> | |||
| Save | |||
| </Button> | |||
| </div> | |||
| <div className="right-buttons"> | |||
| <Button className="photo-save" variant="contained" color="info" | |||
| onClick={() => navigate(`/${instance.instanceId}`)}> | |||
| Albums <ArrowRightAltOutlined/> | |||
| </Button> | |||
| <div className="card-details-right"> | |||
| <div className="flex flex--vert flex--align-start pad"> | |||
| <span className="inline-block bold">Home layout:</span> | |||
| <div className="flex flex--vert pad"> | |||
| {homeLayouts.map(layout => | |||
| <div key={layout.id} className="flex pointer" onClick={() => handleSelectLayout(idx, layout.id)}> | |||
| <span>{instance.homeLayout === layout.id ? <RadioButtonChecked/> : | |||
| <RadioButtonUnchecked/>} | |||
| </span> | |||
| <span> {layout.name}</span> | |||
| </div> | |||
| )} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className="input-buttons"> | |||
| <div className="left-buttons"> | |||
| <Button className="photo-save" variant="contained" color="error" | |||
| onClick={(e) => handleDeleteInstance(e, instance.instanceId)}> | |||
| Delete | |||
| </Button> | |||
| <Button className="photo-save" variant="contained" color="info" | |||
| disabled={!instanceHasChanges(idx)} | |||
| onClick={() => handleSaveInstance(idx)}> | |||
| Save | |||
| </Button> | |||
| </div> | |||
| <div className="right-buttons"> | |||
| <Button className="photo-save" variant="contained" color="info" | |||
| onClick={() => navigate(`/${instance.instanceId}`)}> | |||
| Manage <ArrowRightAltOutlined/> | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| )} | |||
| </div>)} | |||
| </div> | |||
| </div>; | |||
| } | |||
| @@ -13,9 +13,11 @@ | |||
| img { | |||
| position: relative; | |||
| } | |||
| .btn-edit-photo { | |||
| display: none; | |||
| } | |||
| &:hover { | |||
| .btn-edit-photo { | |||
| position: absolute; | |||
| @@ -27,6 +29,7 @@ | |||
| width: 100%; | |||
| height: 100%; | |||
| background-color: rgba(255, 255, 255, 0.2); | |||
| svg { | |||
| color: white; | |||
| font-size: 48px; | |||
| @@ -70,5 +73,4 @@ | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| import {NavLink} from "react-router-dom"; | |||
| import {ArrowRightAlt} from "@mui/icons-material"; | |||
| import "./home-layout1.scss"; | |||
| export function HomeLayout1({instance}) { | |||
| return <div id="homeLayout1"> | |||
| <div className="cover-photo"> | |||
| <img alt={instance.coverPhoto?.name} src={instance.coverPhoto?.url}/> | |||
| </div> | |||
| <div className="instance-details"> | |||
| <div className="box"> | |||
| <h1>{instance.title}</h1> | |||
| <h2>{instance.subtitle}</h2> | |||
| <NavLink to="/portfolio" className="goto-website-link"> | |||
| <span>ENTER</span><ArrowRightAlt/> | |||
| </NavLink> | |||
| </div> | |||
| </div> | |||
| </div>; | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| #home { | |||
| #homeLayout1 { | |||
| height: 100vh; | |||
| width: 100vw; | |||
| display: grid; | |||
| @@ -36,15 +36,17 @@ | |||
| padding: 60px; | |||
| max-width: 620px; | |||
| text-align: center; | |||
| @media(max-width: 600px) { | |||
| padding: 0; | |||
| max-width: 90vw; | |||
| } | |||
| h1 { | |||
| text-align: center; | |||
| display: inline-block; | |||
| font-size: 37px; | |||
| line-height: 1em; | |||
| margin: 0; | |||
| margin-bottom: 22px; | |||
| //font-family: proxima-nova; | |||
| margin: 0 0 22px; | |||
| font-weight: 700; | |||
| font-style: normal; | |||
| text-transform: uppercase; | |||
| @@ -65,29 +67,38 @@ | |||
| letter-spacing: .28em; | |||
| line-height: 1.7em; | |||
| color: #333; | |||
| margin: 0; | |||
| margin-bottom: 36px; | |||
| margin: 0 0 36px; | |||
| } | |||
| .goto-website-link { | |||
| text-rendering: optimizeLegibility; | |||
| white-space: nowrap; | |||
| line-height: 1em; | |||
| //font-family: proxima-nova; | |||
| font-weight: 500; | |||
| font-style: normal; | |||
| font-size: 14px; | |||
| text-transform: uppercase; | |||
| letter-spacing: 0; | |||
| text-align: center; | |||
| padding: 1em calc(1.44em - 0em) 1em 1.44em; | |||
| display: inline-block; | |||
| padding: 1em 1.44em; | |||
| text-decoration: none; | |||
| background-color: transparent; | |||
| color: #333; | |||
| border: 2px solid #333; | |||
| display: inline-block; | |||
| height: calc(22px + 2em); | |||
| transition: background-color 170ms ease-in-out, color 170ms ease-in-out; | |||
| //transition: color 170ms ease-in-out, border-color 170ms ease-in-out; | |||
| span { | |||
| display: inline-block; | |||
| vertical-align: top; | |||
| line-height: 22px; | |||
| font-size: 14px; | |||
| font-weight: 500; | |||
| } | |||
| svg { | |||
| margin-left: 5px; | |||
| font-size: 22px; | |||
| } | |||
| &:hover { | |||
| background-color: black; | |||
| @@ -0,0 +1,20 @@ | |||
| import {NavLink} from "react-router-dom"; | |||
| import {ArrowRightAlt} from "@mui/icons-material"; | |||
| import "./home-layout2.scss"; | |||
| export function HomeLayout2({instance}) { | |||
| return <div id="homeLayout2"> | |||
| <div className="instance-details"> | |||
| <div className="box"> | |||
| <h1>{instance.title}</h1> | |||
| <h2>{instance.subtitle}</h2> | |||
| <NavLink to="/portfolio" className="goto-website-link"> | |||
| <span>ENTER</span><ArrowRightAlt/> | |||
| </NavLink> | |||
| </div> | |||
| </div> | |||
| <div className="cover-photo"> | |||
| <img alt={instance.coverPhoto?.name} src={instance.coverPhoto?.url}/> | |||
| </div> | |||
| </div>; | |||
| } | |||
| @@ -0,0 +1,110 @@ | |||
| #homeLayout2 { | |||
| height: 100vh; | |||
| width: 100vw; | |||
| display: grid; | |||
| grid-template-columns: 1fr 1fr; | |||
| @media(max-width: 1200px) { | |||
| grid-template-columns: 1fr; | |||
| grid-template-rows: 1fr 1fr; | |||
| } | |||
| .cover-photo { | |||
| overflow: hidden; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| img { | |||
| width: 100%; | |||
| height: 100%; | |||
| object-position: 50% 50%; | |||
| object-fit: cover; | |||
| } | |||
| } | |||
| .instance-details { | |||
| display: flex; | |||
| justify-content: center; | |||
| flex-direction: column; | |||
| .box { | |||
| position: static !important; | |||
| transform: none; | |||
| margin-left: auto; | |||
| margin-right: auto; | |||
| padding: 60px; | |||
| max-width: 620px; | |||
| text-align: center; | |||
| @media(max-width: 600px) { | |||
| padding: 0; | |||
| max-width: 90vw; | |||
| } | |||
| h1 { | |||
| text-align: center; | |||
| display: inline-block; | |||
| font-size: 37px; | |||
| line-height: 1em; | |||
| margin: 0 0 22px; | |||
| font-weight: 700; | |||
| font-style: normal; | |||
| text-transform: uppercase; | |||
| letter-spacing: .1em; | |||
| color: #333; | |||
| @media(max-width: 600px) { | |||
| font-size: 24px; | |||
| } | |||
| } | |||
| h2 { | |||
| //font-family: minion-pro; | |||
| font-size: 19px; | |||
| font-weight: 400; | |||
| font-style: normal; | |||
| text-transform: uppercase; | |||
| letter-spacing: .28em; | |||
| line-height: 1.7em; | |||
| color: #333; | |||
| margin: 0 0 36px; | |||
| } | |||
| .goto-website-link { | |||
| text-rendering: optimizeLegibility; | |||
| white-space: nowrap; | |||
| //font-family: proxima-nova; | |||
| font-style: normal; | |||
| text-transform: uppercase; | |||
| letter-spacing: 0; | |||
| text-align: center; | |||
| padding: 1em 1.44em; | |||
| text-decoration: none; | |||
| background-color: transparent; | |||
| color: #333; | |||
| border: 2px solid #333; | |||
| display: inline-block; | |||
| height: calc(22px + 2em); | |||
| transition: background-color 170ms ease-in-out, color 170ms ease-in-out; | |||
| span { | |||
| display: inline-block; | |||
| vertical-align: top; | |||
| line-height: 22px; | |||
| font-size: 14px; | |||
| font-weight: 500; | |||
| } | |||
| svg { | |||
| margin-left: 5px; | |||
| font-size: 22px; | |||
| } | |||
| &:hover { | |||
| background-color: black; | |||
| color: white; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| import {NavLink} from "react-router-dom"; | |||
| import {ArrowRightAlt} from "@mui/icons-material"; | |||
| import "./home-layout3.scss"; | |||
| export function HomeLayout3({instance}) { | |||
| return <div id="homeLayout3"> | |||
| <div className="cover-photo"> | |||
| <img alt={instance.coverPhoto?.name} src={instance.coverPhoto?.url}/> | |||
| <div className="instance-details"> | |||
| <div className="box"> | |||
| <h1>{instance.title}</h1> | |||
| <h2>{instance.subtitle}</h2> | |||
| <NavLink to="/portfolio" className="goto-website-link"> | |||
| <span>ENTER</span><ArrowRightAlt/> | |||
| </NavLink> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div>; | |||
| } | |||
| @@ -0,0 +1,110 @@ | |||
| #homeLayout3 { | |||
| height: 100vh; | |||
| width: 100vw; | |||
| .cover-photo { | |||
| overflow: hidden; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| img { | |||
| position: absolute; | |||
| top: 0; | |||
| width: 100%; | |||
| height: 100%; | |||
| object-position: 50% 50%; | |||
| object-fit: cover; | |||
| } | |||
| } | |||
| .instance-details { | |||
| margin-top: calc(50vh - 125px); | |||
| height: 250px; | |||
| width: 100vw; | |||
| background-color: white; | |||
| display: flex; | |||
| justify-content: center; | |||
| flex-direction: column; | |||
| position: relative; | |||
| .box { | |||
| position: static !important; | |||
| transform: none; | |||
| margin-left: auto; | |||
| margin-right: auto; | |||
| padding: 60px; | |||
| max-width: 620px; | |||
| text-align: center; | |||
| @media(max-width: 600px) { | |||
| padding: 0; | |||
| max-width: 90vw; | |||
| } | |||
| h1 { | |||
| text-align: center; | |||
| display: inline-block; | |||
| font-size: 37px; | |||
| line-height: 1em; | |||
| margin: 0 0 22px; | |||
| font-weight: 700; | |||
| font-style: normal; | |||
| text-transform: uppercase; | |||
| letter-spacing: .1em; | |||
| color: #333; | |||
| @media(max-width: 600px) { | |||
| font-size: 24px; | |||
| } | |||
| } | |||
| h2 { | |||
| //font-family: minion-pro; | |||
| font-size: 19px; | |||
| font-weight: 400; | |||
| font-style: normal; | |||
| text-transform: uppercase; | |||
| letter-spacing: .28em; | |||
| line-height: 1.7em; | |||
| color: #333; | |||
| margin: 0 0 36px; | |||
| } | |||
| .goto-website-link { | |||
| text-rendering: optimizeLegibility; | |||
| white-space: nowrap; | |||
| //font-family: proxima-nova; | |||
| font-style: normal; | |||
| text-transform: uppercase; | |||
| letter-spacing: 0; | |||
| text-align: center; | |||
| padding: 1em 1.44em; | |||
| text-decoration: none; | |||
| background-color: transparent; | |||
| color: #333; | |||
| border: 2px solid #333; | |||
| display: inline-block; | |||
| height: calc(22px + 2em); | |||
| transition: background-color 170ms ease-in-out, color 170ms ease-in-out; | |||
| span { | |||
| display: inline-block; | |||
| vertical-align: top; | |||
| line-height: 22px; | |||
| font-size: 14px; | |||
| font-weight: 500; | |||
| } | |||
| svg { | |||
| margin-left: 5px; | |||
| font-size: 22px; | |||
| } | |||
| &:hover { | |||
| background-color: black; | |||
| color: white; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -7,5 +7,6 @@ export class Instance { | |||
| this.title = instance?.title; | |||
| this.subtitle = instance?.subtitle; | |||
| this.coverPhoto = instance?.coverPhoto ? new Photo(instance.coverPhoto) : null; | |||
| this.homeLayout = instance?.homeLayout; | |||
| } | |||
| } | |||
| @@ -30,7 +30,7 @@ export default function AlbumPage() { | |||
| dangerouslySetInnerHTML={{__html: selectedAlbum.description.replace(/\n/g, "<br />")}}> | |||
| </div>} | |||
| </div> | |||
| {selectedAlbum.photos && <div id="photo-list" className="photo-grid-layout"> | |||
| {selectedAlbum.photos && <div id="photo-list" className="photo-grid-layout photo-grid-layout--no-details"> | |||
| {selectedAlbum.photos.map((photo, idx) => | |||
| <div className="photo grid-cell" key={photo.photoId} | |||
| onClick={() => setSelectedPhotoIndex(idx)}> | |||
| @@ -41,11 +41,11 @@ export default function AlbumPage() { | |||
| <ZoomOutMapSharp/> | |||
| </div> | |||
| </div> | |||
| <div className="details"> | |||
| <div className="name"> | |||
| {photo.name} | |||
| </div> | |||
| </div> | |||
| {/*<div className="details">*/} | |||
| {/* <div className="name">*/} | |||
| {/* {photo.name}*/} | |||
| {/* </div>*/} | |||
| {/*</div>*/} | |||
| </div> | |||
| )} | |||
| </div>} | |||
| @@ -1,23 +1,15 @@ | |||
| import {useCurrentInstance} from "../../services/current-instance/use-current-instance"; | |||
| import "./home-page.scss"; | |||
| import {NavLink} from "react-router-dom"; | |||
| import {HomeLayout1} from "../../components/home-layouts/home-layout1"; | |||
| import {HomeLayout2} from "../../components/home-layouts/home-layout2"; | |||
| import {HomeLayout3} from "../../components/home-layouts/home-layout3"; | |||
| export function HomePage() { | |||
| const {instance} = useCurrentInstance(); | |||
| return instance && <div id="home"> | |||
| <div className="cover-photo"> | |||
| <img alt={instance.coverPhoto?.name} src={instance.coverPhoto?.url}/> | |||
| </div> | |||
| <div className="instance-details"> | |||
| <div className="box"> | |||
| <h1>{instance.title}</h1> | |||
| <h2>{instance.subtitle}</h2> | |||
| <NavLink to="/portfolio" className="goto-website-link"> | |||
| GO TO WEBSITE | |||
| </NavLink> | |||
| </div> | |||
| </div> | |||
| </div>; | |||
| return instance && instance.homeLayout === 3 | |||
| ? <HomeLayout3 instance={instance}/> | |||
| : instance.homeLayout === 2 | |||
| ? <HomeLayout2 instance={instance}/> | |||
| : <HomeLayout1 instance={instance}/> | |||
| ; | |||
| } | |||
| @@ -191,6 +191,10 @@ html:has(#photoViewer) { | |||
| column-gap: 30px; | |||
| margin-bottom: 4em; | |||
| &.photo-grid-layout--no-details { | |||
| row-gap: 30px; | |||
| } | |||
| //@media (max-width: 1920px) { | |||
| // grid-template-columns: repeat(4, 1fr); | |||
| //} | |||
| @@ -10,6 +10,7 @@ export class Instance { | |||
| this.title = instance?.title; | |||
| this.subtitle = instance?.subtitle; | |||
| this.coverPhoto = instance?.coverPhoto ? new Photo(instance?.coverPhoto) : null; | |||
| this.homeLayout = instance?.homeLayout; | |||
| this.urls = instance?.urls || []; | |||
| } | |||
| @@ -23,6 +24,8 @@ export class Instance { | |||
| subtitle; | |||
| /** @type {Photo} **/ | |||
| coverPhoto; | |||
| /** @type {Number} **/ | |||
| homeLayout; | |||
| /** @type {string[]} **/ | |||
| urls; | |||
| } | |||
| @@ -38,6 +38,7 @@ class InstancesService { | |||
| instance.title = instanceToUpdate.title; | |||
| instance.subtitle = instanceToUpdate.subtitle; | |||
| instance.urls = instanceToUpdate.urls; | |||
| instance.homeLayout = instanceToUpdate.homeLayout; | |||
| try { | |||
| await db.instances.update(instance); | |||