aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/ExtractColors.ts57
-rw-r--r--src/client/views/PropertiesView.scss9
-rw-r--r--src/client/views/PropertiesView.tsx42
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx53
4 files changed, 152 insertions, 9 deletions
diff --git a/src/client/views/ExtractColors.ts b/src/client/views/ExtractColors.ts
new file mode 100644
index 000000000..f78d9a355
--- /dev/null
+++ b/src/client/views/ExtractColors.ts
@@ -0,0 +1,57 @@
+import { extractColors } from 'extract-colors';
+
+// Manages image color extraction
+export class ExtractColors {
+ // loads all images into img elements
+ static loadImages = async (imageFiles: File[]): Promise<HTMLImageElement[]> => {
+ try {
+ const imageElements = await Promise.all(imageFiles.map(file => this.loadImage(file)));
+ return imageElements;
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
+ };
+
+ // loads a single img into an img element
+ static loadImage = (file: File): Promise<HTMLImageElement> => {
+ return new Promise((resolve, reject) => {
+ const img = new Image();
+
+ img.onload = () => resolve(img);
+ img.onerror = error => reject(error);
+
+ const url = URL.createObjectURL(file);
+ img.src = url;
+ });
+ };
+
+ // loads all images into img elements
+ static loadImagesUrl = async (imageUrls: string[]): Promise<HTMLImageElement[]> => {
+ try {
+ const imageElements = await Promise.all(imageUrls.map(url => this.loadImageUrl(url)));
+ return imageElements;
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
+ };
+
+ // loads a single img into an img element
+ static loadImageUrl = (url: string): Promise<HTMLImageElement> => {
+ return new Promise((resolve, reject) => {
+ const img = new Image();
+
+ img.onload = () => resolve(img);
+ img.onerror = error => reject(error);
+
+ img.src = url;
+ });
+ };
+
+ // extracts a list of collors from an img element
+ static getImgColors = async (img: HTMLImageElement) => {
+ const colors = await extractColors(img, { distance: 0.35 });
+ return colors;
+ };
+}
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 2da2ec568..ffcad0e7e 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -7,6 +7,15 @@
position: absolute;
right: 4;
}
+.propertiesView-palette {
+ cursor: pointer;
+ padding: 8px;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+ &:hover {
+ background-color: #3b3c3e;
+ }
+}
.propertiesView {
height: 100%;
width: 250;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 64b2a9d65..fb2d811f4 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -40,6 +40,7 @@ import { PropertiesSection } from './PropertiesSection';
import './PropertiesView.scss';
import { DefaultStyleProvider } from './StyleProvider';
import { FaFillDrip } from 'react-icons/fa';
+import { GeneratedResponse } from '../apis/gpt/customization';
const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
@@ -90,6 +91,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable openAppearance: boolean = true;
@observable openTransform: boolean = true;
@observable openFilters: boolean = false;
+ @observable openStyling: boolean = true;
+
+ // GPT styling
+ @observable generatedStyles: GeneratedResponse[] = [];
+ @observable inputDocs: Doc[] = [];
+ @observable selectedStyle: number = -1;
+
+ @action
+ setGeneratedStyles = (responses: GeneratedResponse[]) => (this.generatedStyles = responses);
+ setInputDocs = (docs: Doc[]) => (this.inputDocs = docs);
//Pres Trails booleans:
@observable openPresTransitions: boolean = true;
@@ -1161,6 +1172,36 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
};
+ @action
+ styleCollection = (i: number) => {
+ this.selectedStyle = i;
+ const resObj = this.generatedStyles[i];
+ if (this.selectedDoc && this.selectedDoc.type === 'collection') {
+ this.selectedDoc.backgroundColor = resObj.collectionBackgroundColor;
+ resObj.documentsWithColors.forEach((elem, i) => (this.inputDocs[i].backgroundColor = elem.color));
+ }
+ };
+
+ // GPT styling
+ @computed get stylingSubMenu() {
+ return (
+ <PropertiesSection title="Styling" isOpen={this.openStyling} setIsOpen={bool => (this.openStyling = bool)} onDoubleClick={() => this.CloseAll()}>
+ <div className="propertiesView-content" style={{ position: 'relative', height: 'auto', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px' }}>
+ {this.generatedStyles.length > 0
+ ? this.generatedStyles.map((style, i) => (
+ <div className="propertiesView-palette" style={{ display: 'flex', gap: '4px', backgroundColor: this.selectedStyle === i ? StrCast(Doc.UserDoc().userVariantColor) : '#00000000' }} onClick={() => this.styleCollection(i)}>
+ <div style={{ width: '24px', height: '24px', backgroundColor: style.collectionBackgroundColor, borderRadius: '2px' }}></div>
+ {style.documentsWithColors.map(c => (
+ <div style={{ width: '24px', height: '24px', backgroundColor: c.color, borderRadius: '2px' }}></div>
+ ))}
+ </div>
+ ))
+ : 'Generating styles...'}
+ </div>
+ </PropertiesSection>
+ );
+ }
+
@computed get filtersSubMenu() {
return (
<PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => (this.openFilters = bool)} onDoubleClick={() => this.CloseAll()}>
@@ -1739,6 +1780,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div className="propertiesView-name">{this.editableTitle}</div>
<div className="propertiesView-type"> {this.currentType} </div>
+ {this.stylingSubMenu}
{this.optionsSubMenu}
{this.linksSubMenu}
{!LinkManager.currentLink || !this.openLinks ? null : this.linkProperties}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 002ebf1ae..fb2de5647 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -27,7 +27,7 @@ import { FollowLinkScript } from '../../../util/LinkFollower';
import { ReplayMovements } from '../../../util/ReplayMovements';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SelectionManager } from '../../../util/SelectionManager';
-import { freeformScrollMode } from '../../../util/SettingsManager';
+import { freeformScrollMode, SettingsManager } from '../../../util/SettingsManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
@@ -52,7 +52,11 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
import React = require('react');
-import { generatePalette } from '../../../apis/gpt/customization';
+import { DocumentWithColor, GeneratedResponse, generatePalette } from '../../../apis/gpt/customization';
+import { PropertiesView } from '../../PropertiesView';
+import { MainView } from '../../MainView';
+import { ExtractColors } from '../../ExtractColors';
+import { extname } from 'path';
export type collectionFreeformViewProps = {
NativeWidth?: () => number;
@@ -1796,22 +1800,50 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
};
+ @action
+ openProperties = () => {
+ SettingsManager.propertiesWidth = 300;
+ };
+
+ choosePath(url: URL) {
+ if (!url?.href) return '';
+ const lower = url.href.toLowerCase();
+ if (url.protocol === 'data') return url.href;
+ if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
+
+ const ext = extname(url.href);
+ return url.href.replace(ext, '_m' + ext);
+ }
+
// gpt styling
@action
gptStyling = async () => {
+ this.openProperties();
console.log('Title', this.rootDoc.title);
console.log('bgcolor', this.layoutDoc._backgroundColor);
// doc.backgroundColor
console.log('styling');
const inputDocs = this.childDocs.filter(doc => doc.type == 'rich text');
+ const imgDocs = this.childDocs.filter(doc => doc.type == 'image');
+ const imgUrls = imgDocs.map(doc => this.choosePath((doc.data as ImageField).url));
+
+ const imageElements = await ExtractColors.loadImagesUrl(imgUrls);
+ const colors = await Promise.all(imageElements.map(elem => ExtractColors.getImgColors(elem)));
+ let colorHexes = colors
+ .reduce((acc, curr) => acc.concat(curr), [])
+ .map(color => color.hex)
+ .slice(0, 10);
+ console.log('Hexes', colorHexes);
+
+ PropertiesView.Instance?.setInputDocs(inputDocs);
+ // also pass it colors
const gptInput = inputDocs.map((doc, i) => ({
id: i,
textContent: (doc.text as RichTextField)?.Text,
textSize: 16,
}));
- // inputDocs[0].backgroundColor = '#3392ff';
-
const collectionDescription = StrCast(this.rootDoc.title);
console.log({
@@ -1823,13 +1855,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const res = await generatePalette({
collectionDescription,
documents: gptInput,
+ imageColors: colorHexes,
});
- console.log('done');
if (typeof res === 'string') {
- const resObj = JSON.parse(res);
- console.log('Result ', resObj);
- this.rootDoc.backgroundColor = resObj.collectionBackgroundColor;
- (resObj.documentsWithColors as any[]).forEach((elem, i) => (inputDocs[i].backgroundColor = elem.color));
+ console.log(res);
+ const resObj = JSON.parse(res) as GeneratedResponse[];
+ PropertiesView.Instance?.setGeneratedStyles(resObj);
+ // const resObj = JSON.parse(res) as GeneratedResponse;
+ // console.log('Result ', resObj);
+ // this.rootDoc.backgroundColor = resObj.collectionBackgroundColor;
+ // (resObj.documentsWithColors).forEach((elem, i) => (inputDocs[i].backgroundColor = elem.color));
}
} catch (err) {
console.error(err);