<commit_before>/// <reference path="../../typings/globals/react/index.d.ts" />
/// <reference path="../../typings/globals/react-dom/index.d.ts" />
/// <reference path="../../built/pxtlib.d.ts" />

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as workspace from "./workspace";
import * as data from "./data";
import * as sui from "./sui";
import * as pkg from "./package";
import * as core from "./core";
import * as compiler from "./compiler";

import * as codecard from "./codecard"
import * as gallery from "./gallery";
import * as carousel from "./carousel";

type ISettingsProps = pxt.editor.ISettingsProps;

import Cloud = pxt.Cloud;

interface ProjectsProps extends ISettingsProps {
    hasGettingStarted: boolean;
}

interface ProjectsState {
    searchFor?: string;
    visible?: boolean;
    tab?: string;
    isInitialStartPage?: boolean;
    welcomeDescription?: string;
}

const HOME = "__welcome";

export class Projects extends data.Component<ProjectsProps, ProjectsState> {
    private prevUrlData: Cloud.JsonScript[] = [];
    private prevGalleries: pxt.Map<pxt.CodeCard[]> = {};
    private galleryFetchErrors: { [tab: string]: boolean } = {};

    constructor(props: ProjectsProps) {
        super(props)
        this.state = {
            visible: false,
            tab: HOME
        }
    }

    hide(closeOnly: boolean = false) {
        if (this.state.isInitialStartPage && closeOnly) {
            // If this was the initial start page and the dialog was close without a selection being made, load the
            // previous project if available or create a new one
            pxt.tickEvent("projects.welcome.hide");
            this.props.parent.newProject();
        }
        this.setState({ visible: false, isInitialStartPage: false });
    }

    showHome() {
        this.props.parent.stopSimulator();
        this.setState({
            visible: true,
            tab: HOME,
            isInitialStartPage: true
        });
    }

    fetchGallery(tab: string, path: string): pxt.CodeCard[] {
        if (this.state.tab != tab) return [];

        let res = this.getData(`gallery:${encodeURIComponent(path)}`) as gallery.Gallery[];
        if (res) {
            if (res instanceof Error) {
                this.galleryFetchErrors[tab] = true;
            } else {
                this.prevGalleries[path] = Util.concat(res.map(g => g.cards));
            }
        }
        return this.prevGalleries[path] || [];
    }

    fetchUrlData(): Cloud.JsonScript[] {
        let scriptid = pxt.Cloud.parseScriptId(this.state.searchFor)
        if (scriptid) {
            let res = this.getData(`cloud-search:${scriptid}`)
            if (res) {
                if (res.statusCode !== 404) {
                    if (!this.prevUrlData) this.prevUrlData = [res]
                    else this.prevUrlData.push(res)
                }
            }
        }
        return this.prevUrlData;
    }

    fetchLocalData(): pxt.workspace.Header[] {
        if (this.state.tab != HOME) return [];

        let headers: pxt.workspace.Header[] = this.getData("header:*")
        if (this.state.searchFor)
            headers = headers.filter(hdr => hdr.name.toLowerCase().indexOf(this.state.searchFor.toLowerCase()) > -1);
        return headers;
    }

    shouldComponentUpdate(nextProps: ISettingsProps, nextState: ProjectsState, nextContext: any): boolean {
        return this.state.visible != nextState.visible
            || this.state.tab != nextState.tab
            || this.state.searchFor != nextState.searchFor
            || this.state.welcomeDescription != nextState.welcomeDescription;
    }

    private numDaysOld(d1: number) {
        let diff = Math.abs((Date.now() / 1000) - d1);
        return Math.floor(diff / (60 * 60 * 24));
    }

    renderCore() {
        const {visible, tab} = this.state;
        const {hasGettingStarted} = this.props;

        const targetTheme = pxt.appTarget.appTheme;
        const galleries = targetTheme.galleries || {};

        // lf("Make")
        // lf("Code")
        // lf("Projects")
        // lf("Examples")
        // lf("Tutorials")

        const headers = this.fetchLocalData();
        const urldata = this.fetchUrlData();
        this.galleryFetchErrors = {};
        const gals = Util.mapMap(galleries, k => this.fetchGallery(k, galleries[k]));

        const chgHeader = (hdr: pxt.workspace.Header) => {
            pxt.tickEvent("projects.header");
            core.showLoading("changeheader", lf("Loading..."));
            this.hide();
            this.props.parent.loadHeaderAsync(hdr)
                .done(() => {
                    core.hideLoading("changeheader");
                })
        }
        const chgGallery = (scr: pxt.CodeCard) => {
            pxt.tickEvent("projects.gallery", { name: scr.name });
            if (!scr.youTubeId || scr.url) this.hide();
            switch (scr.cardType) {
                case "example": chgCode(scr, true); break;
                case "codeExample": chgCode(scr, false); break;
                case "tutorial": this.props.parent.startTutorial(scr.url, scr.name); break;
                default:
                    const m = /^\/#tutorial:([a-z0A-Z0-9\-\/]+)$/.exec(scr.url);
                    if (m) this.props.parent.startTutorial(m[1]);
                    else {
                        if (scr.youTubeId && !scr.url)
                            window.open('https://youtu.be/' + scr.youTubeId, 'yt');
                        else this.props.parent.newEmptyProject(scr.name.toLowerCase(), scr.url);
                    }
            }
        }

        const chgCode = (scr: pxt.CodeCard, loadBlocks?: boolean) => {
            core.showLoading("changingcode", lf("Loading..."));
            gallery.loadExampleAsync(scr.name.toLowerCase(), scr.url)
                .done(opts => {
                    if (opts) {
                        if (loadBlocks) {
                            const ts = opts.filesOverride["main.ts"]
                            return this.props.parent.createProjectAsync(opts)
                                .then(() => {
                                    return compiler.getBlocksAsync()
                                        .then(blocksInfo => compiler.decompileSnippetAsync(ts, blocksInfo))
                                        .then(resp => this.props.parent.updateFileAsync("main.blocks", resp, true))
                                })
                                .done(() => {
                                    core.hideLoading("changingcode");
                                })
                        } else {
                            return this.props.parent.newProject(opts);
                        }
                    }
                    core.hideLoading("changingcode");
                });
        }
        const importProject = () => {<commit_msg>Fix decompiling in the gallery (#3078)<commit_after>/// <reference path="../../typings/globals/react/index.d.ts" />
/// <reference path="../../typings/globals/react-dom/index.d.ts" />
/// <reference path="../../built/pxtlib.d.ts" />

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as workspace from "./workspace";
import * as data from "./data";
import * as sui from "./sui";
import * as pkg from "./package";
import * as core from "./core";
import * as compiler from "./compiler";

import * as codecard from "./codecard"
import * as gallery from "./gallery";
import * as carousel from "./carousel";

type ISettingsProps = pxt.editor.ISettingsProps;

import Cloud = pxt.Cloud;

interface ProjectsProps extends ISettingsProps {
    hasGettingStarted: boolean;
}

interface ProjectsState {
    searchFor?: string;
    visible?: boolean;
    tab?: string;
    isInitialStartPage?: boolean;
    welcomeDescription?: string;
}

const HOME = "__welcome";

export class Projects extends data.Component<ProjectsProps, ProjectsState> {
    private prevUrlData: Cloud.JsonScript[] = [];
    private prevGalleries: pxt.Map<pxt.CodeCard[]> = {};
    private galleryFetchErrors: { [tab: string]: boolean } = {};

    constructor(props: ProjectsProps) {
        super(props)
        this.state = {
            visible: false,
            tab: HOME
        }
    }

    hide(closeOnly: boolean = false) {
        if (this.state.isInitialStartPage && closeOnly) {
            // If this was the initial start page and the dialog was close without a selection being made, load the
            // previous project if available or create a new one
            pxt.tickEvent("projects.welcome.hide");
            this.props.parent.newProject();
        }
        this.setState({ visible: false, isInitialStartPage: false });
    }

    showHome() {
        this.props.parent.stopSimulator();
        this.setState({
            visible: true,
            tab: HOME,
            isInitialStartPage: true
        });
    }

    fetchGallery(tab: string, path: string): pxt.CodeCard[] {
        if (this.state.tab != tab) return [];

        let res = this.getData(`gallery:${encodeURIComponent(path)}`) as gallery.Gallery[];
        if (res) {
            if (res instanceof Error) {
                this.galleryFetchErrors[tab] = true;
            } else {
                this.prevGalleries[path] = Util.concat(res.map(g => g.cards));
            }
        }
        return this.prevGalleries[path] || [];
    }

    fetchUrlData(): Cloud.JsonScript[] {
        let scriptid = pxt.Cloud.parseScriptId(this.state.searchFor)
        if (scriptid) {
            let res = this.getData(`cloud-search:${scriptid}`)
            if (res) {
                if (res.statusCode !== 404) {
                    if (!this.prevUrlData) this.prevUrlData = [res]
                    else this.prevUrlData.push(res)
                }
            }
        }
        return this.prevUrlData;
    }

    fetchLocalData(): pxt.workspace.Header[] {
        if (this.state.tab != HOME) return [];

        let headers: pxt.workspace.Header[] = this.getData("header:*")
        if (this.state.searchFor)
            headers = headers.filter(hdr => hdr.name.toLowerCase().indexOf(this.state.searchFor.toLowerCase()) > -1);
        return headers;
    }

    shouldComponentUpdate(nextProps: ISettingsProps, nextState: ProjectsState, nextContext: any): boolean {
        return this.state.visible != nextState.visible
            || this.state.tab != nextState.tab
            || this.state.searchFor != nextState.searchFor
            || this.state.welcomeDescription != nextState.welcomeDescription;
    }

    private numDaysOld(d1: number) {
        let diff = Math.abs((Date.now() / 1000) - d1);
        return Math.floor(diff / (60 * 60 * 24));
    }

    renderCore() {
        const {visible, tab} = this.state;
        const {hasGettingStarted} = this.props;

        const targetTheme = pxt.appTarget.appTheme;
        const galleries = targetTheme.galleries || {};

        // lf("Make")
        // lf("Code")
        // lf("Projects")
        // lf("Examples")
        // lf("Tutorials")

        const headers = this.fetchLocalData();
        const urldata = this.fetchUrlData();
        this.galleryFetchErrors = {};
        const gals = Util.mapMap(galleries, k => this.fetchGallery(k, galleries[k]));

        const chgHeader = (hdr: pxt.workspace.Header) => {
            pxt.tickEvent("projects.header");
            core.showLoading("changeheader", lf("Loading..."));
            this.hide();
            this.props.parent.loadHeaderAsync(hdr)
                .done(() => {
                    core.hideLoading("changeheader");
                })
        }
        const chgGallery = (scr: pxt.CodeCard) => {
            pxt.tickEvent("projects.gallery", { name: scr.name });
            if (!scr.youTubeId || scr.url) this.hide();
            switch (scr.cardType) {
                case "example": chgCode(scr, true); break;
                case "codeExample": chgCode(scr, false); break;
                case "tutorial": this.props.parent.startTutorial(scr.url, scr.name); break;
                default:
                    const m = /^\/#tutorial:([a-z0A-Z0-9\-\/]+)$/.exec(scr.url);
                    if (m) this.props.parent.startTutorial(m[1]);
                    else {
                        if (scr.youTubeId && !scr.url)
                            window.open('https://youtu.be/' + scr.youTubeId, 'yt');
                        else this.props.parent.newEmptyProject(scr.name.toLowerCase(), scr.url);
                    }
            }
        }

        const chgCode = (scr: pxt.CodeCard, loadBlocks?: boolean) => {
            core.showLoading("changingcode", lf("Loading..."));
            gallery.loadExampleAsync(scr.name.toLowerCase(), scr.url)
                .done(opts => {
                    if (opts) {
                        if (loadBlocks) {
                            return this.props.parent.createProjectAsync(opts)
                                .then(() => {
                                    return compiler.getBlocksAsync()
                                        .then(blocksInfo => compiler.decompileAsync("main.ts", blocksInfo))
                                        .then(resp => {
                                            if (resp.success) {
                                                return this.props.parent.updateFileAsync("main.blocks", resp.outfiles["main.blocks"], true)
                                            }
                                            return Promise.resolve();
                                        })
                                })
                                .done(() => {
                                    core.hideLoading("changingcode");
                                })
                        } else {
                            return this.props.parent.newProject(opts);
                        }
                    }
                    core.hideLoading("changingcode");
                });
        }
        const importProject = () => {
{'fraction_non_alphanumeric': 0.13338073231425523, 'fraction_numerical': 0.0031283327408460717, 'mean_word_length': 1.6529611467370804, 'pattern_counts': {'":': 6, '<': 13, '<?xml version=': 0, '>': 41, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 17, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1745117', 'n_tokens_mistral': 3877, 'n_tokens_neox': 3719, 'n_words': 783}