Commit aed86d72 authored by greg@1m's avatar greg@1m
Browse files

reorg custom-tags folders + better qrcode + pb mobile window fix

parent 3345f64e
......@@ -47,7 +47,7 @@ export const App = () => {
initSocketConnexion().then(serverSocketConfig => {
toggleSocketConnection(true)
api && api.status.ipServer.set(serverSocketConfig.ipServer)
api && api.status.ipsServer.set(serverSocketConfig.ipsServer)
askForFolderScan(openFolders)
})
......
......@@ -218,7 +218,7 @@ export const ContentBlockTagView = (p: {
srcDoc={htmlContent}
className="tag-iframe"
style={{ height: iframeHeight }}
sandbox="allow-scripts allow-same-origin" // allow-same-origin required for ext js caching
sandbox="allow-scripts allow-same-origin allow-popups" // allow-same-origin required for ext js caching
>
</iframe>
{iframeError &&
......
......@@ -75,6 +75,7 @@ export const Lightbox = (p: {
export const lightboxCss = () => `
.lightbox-component {
position: fixed;
z-index: 1002;
top: 0px;
left: 0px;
width: 100vw;
......
......@@ -19,6 +19,7 @@ type ConfigField = {
var: any,
title: string,
modifier: Function
onCustomHtmlClick?: Function
expl?: string
readOnly?: boolean
customHtml?: string
......@@ -51,8 +52,8 @@ export const SettingsPopup = (p: {
if (api) {
const us = api.userSettings
const currProtocol = `${window.location.protocol}//`
const tiroUrl = `${currProtocol}${api.status.ipServer.get[0]}${configClient.global.port}`
const qrcodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${tiroUrl}`
const tiroUrl = `${currProtocol}${api.status.ipsServer.getLocal()}${configClient.global.port}`
const qrcodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=400x400&margin=50&data=${tiroUrl}`
conf = [
{
title: "Devices",
......@@ -60,11 +61,22 @@ export const SettingsPopup = (p: {
{
type: 'text',
var: tiroUrl,
customHtml: `<div class="qrcode-wrapper"><img src="${qrcodeUrl}"/></div>`,
customHtml: `
<div class="qrcode-wrapper">
<img src="${qrcodeUrl}"/>
<br>
</div>`,
title: "Tiro Url",
readOnly: true,
expl: "Access Tiro on another device on the same wifi/local network by entering that url in a browser",
modifier: setBackendPort,
expl: `Access Tiro on another device on the <b>same wifi/local network</b> either by :
<br>
- Entering that url in a browser
<br>
- Scanning that qrcode (on desktop, go to a website like <a href='https://webqr.com/'>webqr.com </a>)`,
modifier: () => { },
onCustomHtmlClick: () => {
api.ui.lightbox.open(0, [qrcodeUrl])
}
},
],
},
......@@ -143,6 +155,7 @@ export const SettingsPopup = (p: {
field.customHtml &&
<div
className="custom-html-wrapper"
onClick={e => { field.onCustomHtmlClick && field.onCustomHtmlClick() }}
dangerouslySetInnerHTML={{
__html: field.customHtml
}}
......@@ -150,7 +163,12 @@ export const SettingsPopup = (p: {
}
</div>
<div className="explanation">{field.expl}</div>
<div
className="explanation"
dangerouslySetInnerHTML={{
__html: field.expl || ""
}}
></div>
</div>
)
}
......@@ -159,7 +177,7 @@ export const SettingsPopup = (p: {
</div>
)
}
</Popup>
</Popup >
</div >
)
}
......@@ -218,6 +236,7 @@ export const settingsPopupCss = () => `
padding-bottom: 11px;
align-items: center;
.input-and-html-wrapper {
width: 50%;
display: flex;
.input-component {
flex: 1;
......@@ -230,9 +249,11 @@ export const settingsPopupCss = () => `
margin-left: 20px;
.qrcode-wrapper {
img {
margin: 5px 25px 0px 0px;
cursor: pointer;
width: 50px;
&:hover {
width: 150px;
// width: 150px;
}
}
}
......
......@@ -5,7 +5,7 @@ import '../../../node_modules/react-grid-layout/css/styles.css'
import '../../../node_modules/react-resizable/css/styles.css'
import { iFile, iGrid, iViewType, iWindow, iWindowContent } from '../../../../shared/types.shared';
import { increment } from '../../../../shared/helpers/number.helper';
import { addNewWindowConfig } from '../../hooks/app/tabs.hook';
import { addNewWindowConfig, iWindowLayoutAndContent } from '../../hooks/app/tabs.hook';
import { useResize } from '../../hooks/useResize.hook';
import { WindowEditor } from './WindowEditor.component';
import { cssVars } from '../../managers/style/vars.style.manager';
......@@ -245,16 +245,31 @@ p.onGridUpdate({ layout: intLayout, content: intContent })
}
const api = useContext(ClientApiContext)
const refresh = api?.status.refresh.get
// const refresh = api?.status.refresh.get
const [mobileWindow, setMobileWindow] = useState<iWindowLayoutAndContent | null>(null)
useEffect(() => {
// make first window active if mobile
if (deviceType() === 'mobile' && intLayout[0]) {
const wid = intLayout[0].i
makeWindowActive(wid, intContent[0].file);
// make mobile window
if (deviceType() === 'mobile') {
const activeWindow = api?.ui.windows.active.get()
// either the active one
if (activeWindow) setMobileWindow(activeWindow)
// the first one, then make that one active
else {
const first: iWindowLayoutAndContent = { layout: intLayout[0], content: intContent[0] }
if (first.layout && first.content && first.layout.i === first.content.i) {
setMobileWindow(first)
makeWindowActive(first.content.i)
}
}
}
}, [refresh])
}, [p.refresh])
// const mobileWindowdow: = {
// layout: activeWindow?.layout,
// content: activeWindow?.content
// }
const WindowTools = (window, i) => {
return (//jsx
<>
......@@ -341,10 +356,25 @@ p.onGridUpdate({ layout: intLayout, content: intContent })
<div className="mobile-grid-view">
<div className=" window-wrapper">
<div className="note-wrapper">
<WindowEditor
content={p.grid.content[0] && p.grid.content[0]}
onViewChange={(nView) => { viewTypeChange(nView, 0) }}
/>
111
{
mobileWindow && mobileWindow.content.i
}
222
{
mobileWindow && mobileWindow.layout.i
}
333
{
api?.ui.windows.active.get()?.content.file?.name
}
444
{mobileWindow &&
<WindowEditor
content={mobileWindow.content}
onViewChange={(nView) => { viewTypeChange(nView, 0) }}
/>
}
</div>
</div>
</div>
......
......@@ -101,7 +101,7 @@ export const useBrowserApi = (p: {
// sort them
const sortMode = p.userSettingsApi.get('ui_filesList_sortMode')
const nfilesSorted = sortFiles(nfiles, sortMode)
let activeIndex = 0
let activeIndex = -1
// if search for a file title
if (fileTitle) {
......
import { each } from "lodash"
import { useState } from "react"
export interface iStatusApi {
isConnected: boolean,
ipServer: {
ipsServer: {
get: string[]
set: (nIps: string[]) => void
getLocal: () => string
}
searching: {
get: boolean
......@@ -27,12 +29,19 @@ export const useStatusApi = (p: {
const [isSearching, setIsSearching] = useState(false)
const [ipServer, setIpServer] = useState<string[]>([])
const [ipsServer, setIpsServer] = useState<string[]>([])
const getLocalIpServer = () => {
let res = ipsServer[0]
each(ipsServer, ip => {
if (ip.startsWith('192.168')) res = ip
})
return res
}
return {
isConnected: p.isConnected,
searching: { get: isSearching, set: setIsSearching },
ipServer: { get: ipServer, set: setIpServer },
ipsServer: { get: ipsServer, set: setIpsServer, getLocal: getLocalIpServer },
refresh: {
...p.refresh,
increment: () => { p.refresh.set(p.refresh.get + 1) }
......
......@@ -26,11 +26,12 @@ export type iWindowsApi = {
updateWindows: (windowIds: string[], file: iFile) => void
getIdsFromFile: (filepath: string) => string[]
active: {
get: (tab: iTab) => { layout: iWindow, content: iWindowContent } | null
get: (tab?: iTab) => iWindowLayoutAndContent | null
setContent: (file: iFile) => void
}
}
export interface iWindowLayoutAndContent { layout: iWindow, content: iWindowContent }
export const addNewWindowConfig = (p: {
file: iFile,
......@@ -245,6 +246,14 @@ export const useTabs = () => {
const getActiveWindow: iWindowsApi['active']['get'] = tab => {
if (!tab) {
// get active tab
const nTabs = cloneDeep(tabs)
const aId = getActiveTabIndex(nTabs)
if (!isNumber(aId)) return
tab = nTabs[aId]
}
if (!tab.grid.layout[0]) return
const g = tab.grid
let res
......@@ -261,12 +270,15 @@ export const useTabs = () => {
const nTabs = cloneDeep(tabs)
const aId = getActiveTabIndex(nTabs)
if (!isNumber(aId)) return
// get active window, if none, select first one
const aTab = nTabs[aId]
const aContent = aTab.grid.content
if (aContent.length < 1) return
let aWindowIndex = 0
each(aContent, (window, index) => { if (window.active === true) aWindowIndex = index })
console.log(h2, "4455", aContent[aWindowIndex].i, aWindowIndex);
// change awindow.file
aContent[aWindowIndex].file = cloneDeep(nFile)
// update tab name only if tab name not manually edited
......
import { each, isString } from 'lodash';
import React, { useEffect, useRef, useState } from 'react';
import { iFileImage } from '../../../../shared/types.shared';
import { iFile, iFileImage } from '../../../../shared/types.shared';
export interface iLightboxApi {
open: (index: number, images: iFileImage[]) => void
open: (index: number, images: iFileImage[] | string[]) => void
close: () => void
}
......@@ -13,7 +14,23 @@ export const useLightbox = () => {
const openLightbox: iLightboxApi['open'] = (index, images) => {
console.log(`[LIGHTBOX] open ${images.length} images to index ${index}`, { images });
setLightboxImages(images)
const imgsRes: iFileImage[] = []
// if imgs are just string, generate a empty fileImage object around it
each(images, img => {
if (isString(img)) {
const nImg: iFileImage = {
file: generateEmptyiFile(),
title: '',
url: img
}
imgsRes.push(nImg)
} else {
imgsRes.push(img as iFileImage)
}
})
setLightboxImages(imgsRes)
setLigthboxIndex(index)
}
const closeLightbox: iLightboxApi['close'] = () => {
......@@ -33,3 +50,13 @@ export const useLightbox = () => {
}
export const generateEmptyiFile = (): iFile => {
return {
nature: 'file',
name: '',
realname: '',
path: '',
folder: '',
}
}
......@@ -23,8 +23,8 @@ export const getBackendUrl = () => {
export interface iServerSocketConfig {
socket: SocketIOClient.Socket
ipServer: string[],
socketManager: ClientSocketManager<iApiDictionary>
ipsServer: string[],
socketManager: ClientSocketManager<iApiDictionary>
}
export const initSocketConnexion = (): Promise<iServerSocketConfig> => {
return new Promise((resolve, reject) => {
......@@ -41,7 +41,7 @@ export const initSocketConnexion = (): Promise<iServerSocketConfig> => {
if (!data.isRgGood) alert(strings.rgNotWorking);
resolve({
socket: clientSocket,
ipServer: data.ipServer,
ipsServer: data.ipsServer,
socketManager: clientSocket2
})
})
......
let React = window.React
let ReactDOM = window.ReactDOM
const rssApp = (innerTagStr, opts) => {
if (!opts) opts = {}
if (!opts.size) opts.size = "95%"
if (!opts.rssToJsonUrl) opts.rssToJsonUrl = "https://api.rss2json.com/v1/api.json?rss_url="
const h = `[CTAG RSS] 0431 v1.1`
//@ts-ignore
const api = window.api;
const { div, updateContent } = api.utils.createDiv();
const divId = `rss-${api.utils.uuid()}`;
console.log(h, "========= INIT with opts:", opts)
const execRssReader = (feedsStr) => {
const getFeeds = (str) => {
const feedsArr = str.split('\n')
const feedsRes = []
for (let i = 0; i < feedsArr.length; i++) {
const raw = feedsArr[i].trim().split(" ")
if (raw.length !== 2) continue
feedsRes.push({ name: raw[0], url: raw[1] })
}
console.log(h, "1: gettings feedsRefs Arr", feedsRes)
return feedsRes
}
const getJsons = (cb) => {
const feedsArr = getFeeds(feedsStr)
const urlApi = opts.rssToJsonUrl
let resItems = []
let count = 0
for (let i = 0; i < feedsArr.length; i++) {
fetch(`${urlApi}${feedsArr[i].url}`)
.then(response => {
return response.json()
})
.then(data => {
count = count + 1
console.log(h, "1 debug els", feedsArr[i].url, data, count, feedsArr.length)
if (Array.isArray(data.items)) {
const nitems = [...data.items]
for (let j = 0; j < nitems.length; j++) {
nitems[j].sourceRss = feedsArr[i].name
const timestamp = Date.parse(nitems[j].pubDate)
const d = new Date(timestamp)
//const datestring = d.getDate() + "-" + (d.getMonth()+1) + "-" + d.getFullYear() + " " + d.getHours() + ":" + d.getMinutes();
const datestring = d.getDate() + "/" + (d.getMonth() + 1) + " " + d.getHours() + ":" + d.getMinutes();
nitems[j].timestamp = timestamp
nitems[j].smallDate = datestring
resItems.push(nitems[j])
}
}
if (count === feedsArr.length) {
// sort items by time
console.log(h, "2: outputting elements taken from api")
resItems = resItems.sort((a, b) => b.timestamp - a.timestamp)
cb(resItems)
}
});
}
}
const c = React.createElement;
const ArticleDetail = (p) => {
const [type, setType] = React.useState("text")
React.useEffect(() => {
// for mp3/podcasts
if (
p.article.enclosure.type &&
p.article.enclosure.type.includes("audio")
) setType("audio")
}, [p.article])
const bgColors = ["#264653", "#2A9D8F", "#E9C46A", "#F4A261", "#E76F51"]
const cColor = bgColors[Math.floor(Math.random() * bgColors.length)];
const bgImage = p.article.thumbnail || p.article.enclosure.link
const openLinkNewWindow =(url) => {
window.open(url, '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=no');
}
return (
c('div', { className: "article-details-wrapper" }, [
c('div', { className: "article-close", onClick: () => { p.onClose() } }, ["x"]),
c('div', { className: "article-title" }, [p.article.title]),
c('div', {
className: "bg-image",
style: {
backgroundImage: "url(" + bgImage + ")",
backgroundColor: cColor
}
}),
c('div', { className: "article-content-wrapper" }, [
c('div', { className: "article-time" }, [
p.article.pubDate + " - " + p.article.sourceRss
]),
c('div', {
className: "article-description",
dangerouslySetInnerHTML: { __html: p.article.description }
}),
c('br'),
p.article.content !== p.article.description && c('div', {
className: "article-description",
dangerouslySetInnerHTML: { __html: p.article.content }
}),
]),
type !== "audio" && c('a', { className: "article-link", href: p.article.link, target: "_blank" }, ["link"]),
type === "audio" &&
c('div', { className: "audio-wrapper", }, [
c('audio', { controls: "true" }, [
c('source', {
src: p.article.enclosure.link,
type: p.article.enclosure.type
})
]),
c('a', { className: "article-link", href: p.article.enclosure.link, target: "_blank" }, ["audio file"]),
])
])
)
};
const App = () => {
const [items, setItems] = React.useState([])
const [itemActive, setItemActive] = React.useState(null)
React.useEffect(() => {
getJsons(nitems => {
const i = [...nitems]
setItems(i)
})
}, [])
return (
c('div', { className: "rss-app-wrapper" }, [
c('div', { className: "articles-list" }, [
items.map(item =>
c('div', {
className: "article-list-item",
onClick: () => { setItemActive(item) }
},
[`[${item.sourceRss} ${item.smallDate}] ${item.title}`]
),
)
]),
itemActive && c(ArticleDetail, {
article: itemActive,
onClose: () => {
setItemActive(null)
}
}, []),
])
);
}
ReactDOM.render(
c(App),
document.getElementById("root-react")
);
}
api.utils.loadScripts(
[
"https://unpkg.com/react@18/umd/react.production.min.js",
"https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
],
() => {
console.log(h, "0: react loaded, starting script with innertag:", innerTagStr)
execRssReader(innerTagStr)
setTimeout(() => {
api.utils.resizeIframe(opts.size);
}, 100);
}
);
const styleRss = `
html, body,
#root-react {
height: 100vh;
overflow: hidden;
}
body {
font-family: sans-serif;
padding: 0px;
margin: 0px;
}
.rss-app-wrapper {
position: relative;
height: 100%;
}
/* DETAILS */
.article-details-wrapper {
width: 50%;
padding: 15px;
position: absolute;
background: white;
height: calc(100% - 110px);
overflow-y: scroll;
height: 100%;
right: 0px;
top: 0px;
box-shadow: 0px 0px 17px rgb(0 0 0 / 25%);
}
@media screen and (max-width: 500px) {
.article-details-wrapper {
width: calc(100% - 30px);
}
}
.article-title {
position: relative;
z-index: 1;
font-size: 16px;
font-weight: bold;
background: white;
padding: 10px;
margin-top: 80px;
font-size: 16px;
}
.article-time {
color: grey;
font-size: 10px;
font-weight: 800;
position: relative;
bottom: 10px;
}
.article-content-wrapper {
padding-top: 16px;
background: white;
position: relative;