Module ezprez.core

The module that contains the Presentation and Slide classes that are used to generate web presentations

Classes

Slide

The class that is used to generate slides that are fed into the Presentation object

Presentation

The class for defining the presentation configuration, and primary entrypoint to exporting presentations

Notes

  • On first run you will need an internet connection to download webslides

Examples

Creating a presentation with a slide and exporting it to ./Presentation

from ezprez.core import Presentation, Slide

# This slide is auto-added to the Presentation object by default
Slide('This is the title', 'and this is the content')

prez = Presentation(title, description, url)

# Export the files to the current directory at /Presentation, and delete existing files if they're found
prez.export(".", force=True, folder_name="Presentation")
Expand source code
"""The module that contains the Presentation and Slide classes that are used to generate web presentations

Classes
-------
#### Slide
The class that is used to generate slides that are fed into the Presentation object

####Presentation
The class for defining the presentation configuration, and primary entrypoint to exporting presentations

Notes
-----
- On first run you will need an internet connection to download webslides

Examples
--------
#### Creating a presentation with a slide and exporting it to ./Presentation
```
from ezprez.core import Presentation, Slide

# This slide is auto-added to the Presentation object by default
Slide('This is the title', 'and this is the content')

prez = Presentation(title, description, url)

# Export the files to the current directory at /Presentation, and delete existing files if they're found
prez.export(".", force=True, folder_name="Presentation")
```
"""
# Standard lib dependencies
import os                                   # Used in path validation
from datetime import datetime               # Used to get date for export
from typing import Union, List              # Used to enrich type hints in methods
from shutil import copyfile, copytree, rmtree         # Used to do high level filesystem operations
from dataclasses import dataclass, field    # Used to make class generation faster and more efficient

# Internal dependencies
from ezprez.components import *             # Used for type checking in content generation
from ezprez.components import _Component    # Used for type checking in content generation

# External Dependencies
from tqdm import tqdm                       # Used for progress bars
from elevate import elevate                 # Used for any protected folder access such as system wide python installs or exports
from pystall.core import ZIPResource, build # Used to download webslides from the latest version and extract it for use on export


class Slide:
    """The class that is used to generate slides that are fed into the Presentation object

    Class Variables
    ---------------
    all: (List[Slide])
        Contains all slide instances that have been instatiated, accessed through Slide.all

    Attributes
    ----------
    heading: (str)
        The slide's title/heading

    contents: (str, list, or Component)
        An arbitrary number of strings, lists or Component subclasses that make up the contents of the slide

    background: (False or str)
        The background color of the slide (see notes), optional by default False

    image: (False or Image)
        An image to use as a background for the slide, optional by default False

    horizontal_alignment: (str)
        Where to align the slide contents horizontally, optional defaults to 'center'

    vertical_alignment: (str)
        Where to align the slide contents vertically, optional defaults to 'center'
    
    animation: (str)
        Which animation to use for slide transition, optional defaults to 'fadeIn'

    Notes
    -----
    - Horizontal alignment can be left, right or center
    - Vertical alignment can be top, bottom or center
    - Background colors available can be found at https://webslides.tv/demos/components#slide=27; just make sure to remove 'bg', so for example 'bg-black' becomes 'black'
    - Contents is an unpacked variable, meaning you just specify multiple sets of contents such as Slide(title, 'this is content', ['and', 'so', 'is', 'this'], Button('This to', '#'))
    - Animation can be fadeIn, fadeInUp, zoomIn, slideInLeft, slideInRight
    - You can also add 'slow' to the end of any animation to slow the animation i.e. 'zoomIn slow'


    Examples
    --------
    ### Create a basic text slide
    ```
    from ezprez.core import Slide
    from ezprez.components import Button

    Slide('Here is a text slide', 'and this is the text')
    ```

    ### Create a slide with some text, a list, and a button
    ```
    from ezprez.core import Slide
    from ezprez.components import Button

    Slide('This is the title...', '... and this is content', ['and', 'so', 'is', 'this'], Button('This to', '#'))
    ```
    """
    all = []

    def __init__(self, heading:str, *contents:Union[str, list, tuple, _Component], background:Union[bool,str] = False, horizontal_alignment:str = "center", vertical_alignment:str = "center", image:Union[bool, Image]=False, animation:str = "fadeIn" ):
        # Assign class attributes
        self.image = image
        self.heading = heading
        self.contents = contents
        self.background = background
        self.vertical_alignment = vertical_alignment 
        self.horizontal_alignment = horizontal_alignment
        self.animation = animation

        # Append slide to the class variable Slide.all
        Slide.all.append(self)

    def _generate_content(self):
        """Generates the necessary html with the provided contents"""
        result = f"\n\t\t\t<section class='bg-{self.background} slide-{self.vertical_alignment}'>"
        
        if self.image:
            result += f"\n\t\t\t\t<span class='background' style='background-image:url(\"./static/images/{self.image.filename}\")'></span>"
        result += f"\n\t\t\t\t<div class='wrap {self.animation}'>\n\t\t\t\t\t<div class='content-{self.horizontal_alignment}'>\n\t\t\t\t\t<h2>{self.heading}</h2>\n"
        
        for content in self.contents:
            if type(content) == str: # If the current peice of content is a str
                result += f"\t\t\t\t\t<p>{content}</p>\n"

            elif type(content) == list or type(content) == tuple: # If the current peice of content is a list or tuple
                result += f"\t\t\t\t\t<div class='grid {'content-' + self.horizontal_alignment if not self.horizontal_alignment == 'right' else ''}'>\n\t\t\t\t\t\t\t<ul style='text-align:justify;'>\n"
                for bullet_point in content: # Iterate through each element in the list/tuple
                    result += f"\t\t\t\t\t\t\t\t<li>{bullet_point}</li>\n"
                result += "\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</ul>\n"
            elif isinstance(content, _Component) or type(SocialLink): # If the current peice of content is a Component
                result += content.__html__()
        result += "\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</section>\n"

        yield result


    def __html__(self):
        """Generates the markup for each slide"""
        result = ""
        for content in self._generate_content():
            result+= content
        return result


@dataclass
class Presentation:
    """The class for defining the presentation configuration, and primary entrypoint to exporting presentations

    Attributes
    ----------
    title: (str)
        The title of the presentation

    description: (str)
        The description of the presentation

    url: (str)
        The canonical URL the presentation will be deployed at

    slides: List[Slide]
        The slides to generate the presentation with, optional and defaults to Slide.all

    background: (str)
        What color the intro slie and default slide background color should be, optional defaults to 'white'

    image: (Image or False)
        Path to background image to use for og tags, and intro slide, optional defaults to False

    endcard: (bool)
        Whether to add the endcard, optional defaults to True

    intro: (bool)
        Generate an introduction slide with the background image, title, and description, optional defaults to True

    favicon: (Image or False)
        The image to use for the favicon, optional defaults to False

    vertical: (bool)
        Whether the slideshow should be vertical or not, optional defaults to False

    navbar: (Navbar or False)
        The navbar for the site, optional defaults to False

    footer: (Footer or False)
        The footer for the site, optional defaults to False

    Methods
    -------
    export:
        Exports the presentation files

    Examples
    --------
    ### Creating a presentation and exporting it to ./Presentation
    ```
    from ezprez.core import Presentation

    prez = Presentation(title, description, url)

    # Export the files to the current directory at /Presentation, and delete existing files if they're found
    prez.export(".", force=True, folder_name="Presentation")
    ```

    ### Creating a presentation with a slide and exporting it to ./Presentation
    ```
    from ezprez.core import Presentation, Slide

    # This slide is auto-added to the Presentation object by default
    Slide('This is the title', 'and this is the content')

    prez = Presentation(title, description, url)

    # Export the files to the current directory at /Presentation, and delete existing files if they're found
    prez.export(".", force=True, folder_name="Presentation")
    ```
    """
    title: str 
    description: str 
    url: str
    intro: bool = True
    endcard: bool = True
    vertical: bool = False
    background: str = "white"
    image: Union[bool, Image] = False
    favicon: Union[bool, Image] = False
    navbar: Union[bool, Navbar] = False
    footer: Union[bool, Footer] = False
    slides: List[Slide] = field(default_factory=lambda: Slide.all) 


    def _generate_favicon_markup(self):
        """Generates the html to render the favicon properly"""
        if self.favicon:
            return f'''<!-- FAVICONS -->
            <link rel="apple-touch-icon icon" sizes="76x76" href="./static/images/{self.favicon.filename}">'''
        else:
            return f'''<!-- FAVICONS -->
            <link rel="apple-touch-icon icon" sizes="76x76" href="static/images/favicons/favicon-152.png">'''


    def _generate_intro_slide(self):
        """Generates the first slide in a presentation"""
        if self.intro:
            if self.image:
                return f"""\t\t\t<section class='bg-{self.background}'>
                <span class='background' style='background-image:url("./static/images/{self.image.filename}")'></span>
                    <div class='wrap aligncenter'>
                        <h1><strong>{self.title}</strong></h1>
                        <p class='text-intro'>{self.description}</p>
                    </div>
            </section>"""
            else:
                return f"""\t\t\t<section class='bg-{self.background}'>
                    <div class='wrap aligncenter'>
                        <h1><strong>{self.title}</strong></h1>
                        <p class='text-intro'>{self.description}</p>
                    </div>
            </section>"""


    def _generate_endcard(self) -> str:
        """Generates the slide that goes at the end of Presentation's"""
        if self.endcard:
            return f"""\t\t\t<section class='bg-{self.background}'>
                    <div class='wrap aligncenter'>
                        <h2><strong>Thank you</strong></h2>
                        <h4 class='text-intro'>This presentation was made with the help of</h4>
                        <h4>
                            <strong><em><a href='https://webslides.tv' target='_blank'>WebSlides</a></em></strong> 
                        </h4>
                        <h4>
                            <strong><em><a href='https://github.com/Descent098/ezprez' target='_blank'>ezprez</a></em></strong>
                        </h4>
                    </div>
            </section>"""
        else:
            return ""


    def __len__(self) -> int:
        """Returns the number of slides in the presentation"""
        return len(self.slides)


    def __html__(self) -> str:
        """Generates the index.html file of a presentation using the provided slides"""
        slides_html = ""
        slide_iterator = tqdm(self.slides)
        slide_iterator.set_description_str("Generating slide content")
        for slide in slide_iterator:
            if not slide.background:
                slide.background = self.background
            slides_html += slide.__html__()

        result = f'''<!doctype html>
<html lang="en" prefix="og: http://ogp.me/ns#">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- SEO -->
        <title>{self.title}</title>
        <meta name="description" content="{self.description}">

        <!-- URL CANONICAL -->
        <link rel="canonical" href="{self.url}">

        <!-- Google Fonts -->
        <link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,700,700i%7CMaitree:200,300,400,600,700&amp;subset=latin-ext" rel="stylesheet">

        <!-- CSS WebSlides -->
        <link rel="stylesheet" type='text/css' media='all' href="static/css/webslides.css">

        <!-- Optional - CSS SVG Icons (Font Awesome) -->
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" crossorigin="anonymous" />

        <!-- SOCIAL CARDS (ADD YOUR INFO) -->

        <!-- FACEBOOK -->
        <meta property="og:url" content="{self.url}">
        <meta property="og:type" content="article">
        <meta property="og:title" content="{self.title}"> 
        <meta property="og:description" content="{self.description}">
        <meta property="og:updated_time" content="{datetime.today()}">
        <meta property="og:image" content="{self.image if self.image else "static/images/share-webslides.jpg"}">

        <!-- TWITTER -->
        <meta name="twitter:card" content="summary_large_image">
        <meta name="twitter:title" content="{self.title}"> 
        <meta name="twitter:description" content="{self.description}"> 
        <meta name="twitter:image" content="{self.image if self.image else "static/images/share-webslides.jpg"}">

        {self._generate_favicon_markup()}

        <!-- Android -->
        <meta name="mobile-web-app-capable" content="yes">
        <meta name="theme-color" content="#f0f0f0">

        <!-- Code highlighting -->
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.4.0/styles/default.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.4.0/highlight.min.js"></script>
        <script>hljs.initHighlightingOnLoad();</script>

    </head>
    {self.navbar.__html__() if self.navbar else ""}
    <body>

    <main role='main'>
        <article id='webslides' {'class="vertical"' if self.vertical else ""}>
{self._generate_intro_slide()}
{slides_html}
{self._generate_endcard()}

        </article>
        <!-- end article -->
    </main>
    <!-- end main -->

    <!-- Required -->
    <script src='static/js/webslides.js'></script>
    <script>
        window.ws = new WebSlides();
    </script>

    <!-- OPTIONAL - svg-icons.js (fontastic.me - Font Awesome as svg icons) -->
    <script defer src='static/js/svg-icons.js'></script>

    </body>
    {self.footer.__html__() if self.footer else ""}
</html>
        '''
        return result


    def export(self, file_path:str, folder_name:Union[str, bool] = False, force:bool = False):
        """Exports the presentation files

        Parameters
        ----------
        file_path : (str)
            The path to export the folder of content to

        folder_name : (str or False)
            If you want to overwrite the folder name the output files are put in (defaults to Presentation.title), optional and defaults to False

        force : (bool)
            Whether to force generating files (overwrite existing files if found), optional and defaults to False

        Notes
        -----
        - all files are exported to file_path/folder_name
        - folder_name defaults to Presenation.title

        Raises
        ------
        FileExistsError
            If force is False, and a folder exists at file_path/folder_name

        Examples
        --------
        ### Export a presentation to the current directory at /Presentation
        ```
        from ezprez.core import Presentation
        prez = Presentation(title, description, url)

        # Export the files to the current directory at /Presentation
        prez.export(".", force=True, folder_name="Presentation")
        ```
        """
        if not folder_name:
            folder_name = self.title
        file_path = os.path.abspath(file_path)

        # finding downloads folder
        if os.name == "nt":
            DOWNLOAD_FOLDER = f"{os.getenv('USERPROFILE')}\\Downloads"
        else: # PORT: Assuming variable is there for MacOS and Linux installs
            DOWNLOAD_FOLDER = f"{os.getenv('HOME')}/Downloads" 

        # check if webslides is downloaded, if it is use those files, if not download and extract it
        if not os.path.exists(os.path.join(os.path.dirname(__file__), "webslides")):
            try:
                build(ZIPResource("webslides", "https://webslides.tv/webslides-latest.zip", overwrite_agreement=True))
                copytree(os.path.join(DOWNLOAD_FOLDER, "webslides"), os.path.join(os.path.dirname(__file__), "webslides"))
            except PermissionError:
                elevate()
                copytree(os.path.join(DOWNLOAD_FOLDER, "webslides"), os.path.join(os.path.dirname(__file__), "webslides"))
        try:
            copytree(os.path.join(os.path.dirname(__file__), "webslides"), os.path.join(file_path, folder_name))
        except FileExistsError:
            if force:
                rmtree(os.path.join(file_path, folder_name))
                copytree(os.path.join(os.path.dirname(__file__), "webslides"), os.path.join(file_path, folder_name))
            else:
                raise FileExistsError(f"The file path {os.path.join(file_path, folder_name)} exists, to replace use Presentation.export({file_path}, force=True)")

        # Copy image files
        if os.path.exists("./img"):
            for file_name in os.listdir("./img"):
                if os.path.isfile(os.path.join("img", file_name)):
                    copyfile(os.path.join("img", file_name), os.path.join(file_path, folder_name, "static", "images", file_name))
        elif os.path.exists("./images"):
            for file_name in os.listdir("./images"):
                if os.path.isfile(os.path.join("images", file_name)):
                    copyfile(os.path.join("images", file_name), os.path.join(file_path, folder_name, "static", "images", file_name))

        # replace index.html with generated html
        presentation_content = self.__html__()
        index_path = os.path.join(file_path, folder_name, "index.html")
        print(f"Writing html to {index_path}")
        with open(index_path, "w+") as presentation_file:
            presentation_file.write(presentation_content)

Classes

class Presentation (title: str, description: str, url: str, intro: bool = True, endcard: bool = True, vertical: bool = False, background: str = 'white', image: Union[bool, Image] = False, favicon: Union[bool, Image] = False, navbar: Union[bool, Navbar] = False, footer: Union[bool, Footer] = False, slides: List[Slide] = <factory>)

The class for defining the presentation configuration, and primary entrypoint to exporting presentations

Attributes

title : (str)
The title of the presentation
description : (str)
The description of the presentation
url : (str)
The canonical URL the presentation will be deployed at
slides : List[Slide]
The slides to generate the presentation with, optional and defaults to Slide.all
background : (str)
What color the intro slie and default slide background color should be, optional defaults to 'white'
image : (Image or False)
Path to background image to use for og tags, and intro slide, optional defaults to False
endcard : (bool)
Whether to add the endcard, optional defaults to True
intro : (bool)
Generate an introduction slide with the background image, title, and description, optional defaults to True
favicon : (Image or False)
The image to use for the favicon, optional defaults to False
vertical : (bool)
Whether the slideshow should be vertical or not, optional defaults to False
navbar : (Navbar or False)
The navbar for the site, optional defaults to False
footer : (Footer or False)
The footer for the site, optional defaults to False

Methods

export: Exports the presentation files

Examples

Creating a presentation and exporting it to ./Presentation

from ezprez.core import Presentation

prez = Presentation(title, description, url)

# Export the files to the current directory at /Presentation, and delete existing files if they're found
prez.export(".", force=True, folder_name="Presentation")

Creating a presentation with a slide and exporting it to ./Presentation

from ezprez.core import Presentation, Slide

# This slide is auto-added to the Presentation object by default
Slide('This is the title', 'and this is the content')

prez = Presentation(title, description, url)

# Export the files to the current directory at /Presentation, and delete existing files if they're found
prez.export(".", force=True, folder_name="Presentation")
Expand source code
@dataclass
class Presentation:
    """The class for defining the presentation configuration, and primary entrypoint to exporting presentations

    Attributes
    ----------
    title: (str)
        The title of the presentation

    description: (str)
        The description of the presentation

    url: (str)
        The canonical URL the presentation will be deployed at

    slides: List[Slide]
        The slides to generate the presentation with, optional and defaults to Slide.all

    background: (str)
        What color the intro slie and default slide background color should be, optional defaults to 'white'

    image: (Image or False)
        Path to background image to use for og tags, and intro slide, optional defaults to False

    endcard: (bool)
        Whether to add the endcard, optional defaults to True

    intro: (bool)
        Generate an introduction slide with the background image, title, and description, optional defaults to True

    favicon: (Image or False)
        The image to use for the favicon, optional defaults to False

    vertical: (bool)
        Whether the slideshow should be vertical or not, optional defaults to False

    navbar: (Navbar or False)
        The navbar for the site, optional defaults to False

    footer: (Footer or False)
        The footer for the site, optional defaults to False

    Methods
    -------
    export:
        Exports the presentation files

    Examples
    --------
    ### Creating a presentation and exporting it to ./Presentation
    ```
    from ezprez.core import Presentation

    prez = Presentation(title, description, url)

    # Export the files to the current directory at /Presentation, and delete existing files if they're found
    prez.export(".", force=True, folder_name="Presentation")
    ```

    ### Creating a presentation with a slide and exporting it to ./Presentation
    ```
    from ezprez.core import Presentation, Slide

    # This slide is auto-added to the Presentation object by default
    Slide('This is the title', 'and this is the content')

    prez = Presentation(title, description, url)

    # Export the files to the current directory at /Presentation, and delete existing files if they're found
    prez.export(".", force=True, folder_name="Presentation")
    ```
    """
    title: str 
    description: str 
    url: str
    intro: bool = True
    endcard: bool = True
    vertical: bool = False
    background: str = "white"
    image: Union[bool, Image] = False
    favicon: Union[bool, Image] = False
    navbar: Union[bool, Navbar] = False
    footer: Union[bool, Footer] = False
    slides: List[Slide] = field(default_factory=lambda: Slide.all) 


    def _generate_favicon_markup(self):
        """Generates the html to render the favicon properly"""
        if self.favicon:
            return f'''<!-- FAVICONS -->
            <link rel="apple-touch-icon icon" sizes="76x76" href="./static/images/{self.favicon.filename}">'''
        else:
            return f'''<!-- FAVICONS -->
            <link rel="apple-touch-icon icon" sizes="76x76" href="static/images/favicons/favicon-152.png">'''


    def _generate_intro_slide(self):
        """Generates the first slide in a presentation"""
        if self.intro:
            if self.image:
                return f"""\t\t\t<section class='bg-{self.background}'>
                <span class='background' style='background-image:url("./static/images/{self.image.filename}")'></span>
                    <div class='wrap aligncenter'>
                        <h1><strong>{self.title}</strong></h1>
                        <p class='text-intro'>{self.description}</p>
                    </div>
            </section>"""
            else:
                return f"""\t\t\t<section class='bg-{self.background}'>
                    <div class='wrap aligncenter'>
                        <h1><strong>{self.title}</strong></h1>
                        <p class='text-intro'>{self.description}</p>
                    </div>
            </section>"""


    def _generate_endcard(self) -> str:
        """Generates the slide that goes at the end of Presentation's"""
        if self.endcard:
            return f"""\t\t\t<section class='bg-{self.background}'>
                    <div class='wrap aligncenter'>
                        <h2><strong>Thank you</strong></h2>
                        <h4 class='text-intro'>This presentation was made with the help of</h4>
                        <h4>
                            <strong><em><a href='https://webslides.tv' target='_blank'>WebSlides</a></em></strong> 
                        </h4>
                        <h4>
                            <strong><em><a href='https://github.com/Descent098/ezprez' target='_blank'>ezprez</a></em></strong>
                        </h4>
                    </div>
            </section>"""
        else:
            return ""


    def __len__(self) -> int:
        """Returns the number of slides in the presentation"""
        return len(self.slides)


    def __html__(self) -> str:
        """Generates the index.html file of a presentation using the provided slides"""
        slides_html = ""
        slide_iterator = tqdm(self.slides)
        slide_iterator.set_description_str("Generating slide content")
        for slide in slide_iterator:
            if not slide.background:
                slide.background = self.background
            slides_html += slide.__html__()

        result = f'''<!doctype html>
<html lang="en" prefix="og: http://ogp.me/ns#">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- SEO -->
        <title>{self.title}</title>
        <meta name="description" content="{self.description}">

        <!-- URL CANONICAL -->
        <link rel="canonical" href="{self.url}">

        <!-- Google Fonts -->
        <link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,700,700i%7CMaitree:200,300,400,600,700&amp;subset=latin-ext" rel="stylesheet">

        <!-- CSS WebSlides -->
        <link rel="stylesheet" type='text/css' media='all' href="static/css/webslides.css">

        <!-- Optional - CSS SVG Icons (Font Awesome) -->
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" crossorigin="anonymous" />

        <!-- SOCIAL CARDS (ADD YOUR INFO) -->

        <!-- FACEBOOK -->
        <meta property="og:url" content="{self.url}">
        <meta property="og:type" content="article">
        <meta property="og:title" content="{self.title}"> 
        <meta property="og:description" content="{self.description}">
        <meta property="og:updated_time" content="{datetime.today()}">
        <meta property="og:image" content="{self.image if self.image else "static/images/share-webslides.jpg"}">

        <!-- TWITTER -->
        <meta name="twitter:card" content="summary_large_image">
        <meta name="twitter:title" content="{self.title}"> 
        <meta name="twitter:description" content="{self.description}"> 
        <meta name="twitter:image" content="{self.image if self.image else "static/images/share-webslides.jpg"}">

        {self._generate_favicon_markup()}

        <!-- Android -->
        <meta name="mobile-web-app-capable" content="yes">
        <meta name="theme-color" content="#f0f0f0">

        <!-- Code highlighting -->
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.4.0/styles/default.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.4.0/highlight.min.js"></script>
        <script>hljs.initHighlightingOnLoad();</script>

    </head>
    {self.navbar.__html__() if self.navbar else ""}
    <body>

    <main role='main'>
        <article id='webslides' {'class="vertical"' if self.vertical else ""}>
{self._generate_intro_slide()}
{slides_html}
{self._generate_endcard()}

        </article>
        <!-- end article -->
    </main>
    <!-- end main -->

    <!-- Required -->
    <script src='static/js/webslides.js'></script>
    <script>
        window.ws = new WebSlides();
    </script>

    <!-- OPTIONAL - svg-icons.js (fontastic.me - Font Awesome as svg icons) -->
    <script defer src='static/js/svg-icons.js'></script>

    </body>
    {self.footer.__html__() if self.footer else ""}
</html>
        '''
        return result


    def export(self, file_path:str, folder_name:Union[str, bool] = False, force:bool = False):
        """Exports the presentation files

        Parameters
        ----------
        file_path : (str)
            The path to export the folder of content to

        folder_name : (str or False)
            If you want to overwrite the folder name the output files are put in (defaults to Presentation.title), optional and defaults to False

        force : (bool)
            Whether to force generating files (overwrite existing files if found), optional and defaults to False

        Notes
        -----
        - all files are exported to file_path/folder_name
        - folder_name defaults to Presenation.title

        Raises
        ------
        FileExistsError
            If force is False, and a folder exists at file_path/folder_name

        Examples
        --------
        ### Export a presentation to the current directory at /Presentation
        ```
        from ezprez.core import Presentation
        prez = Presentation(title, description, url)

        # Export the files to the current directory at /Presentation
        prez.export(".", force=True, folder_name="Presentation")
        ```
        """
        if not folder_name:
            folder_name = self.title
        file_path = os.path.abspath(file_path)

        # finding downloads folder
        if os.name == "nt":
            DOWNLOAD_FOLDER = f"{os.getenv('USERPROFILE')}\\Downloads"
        else: # PORT: Assuming variable is there for MacOS and Linux installs
            DOWNLOAD_FOLDER = f"{os.getenv('HOME')}/Downloads" 

        # check if webslides is downloaded, if it is use those files, if not download and extract it
        if not os.path.exists(os.path.join(os.path.dirname(__file__), "webslides")):
            try:
                build(ZIPResource("webslides", "https://webslides.tv/webslides-latest.zip", overwrite_agreement=True))
                copytree(os.path.join(DOWNLOAD_FOLDER, "webslides"), os.path.join(os.path.dirname(__file__), "webslides"))
            except PermissionError:
                elevate()
                copytree(os.path.join(DOWNLOAD_FOLDER, "webslides"), os.path.join(os.path.dirname(__file__), "webslides"))
        try:
            copytree(os.path.join(os.path.dirname(__file__), "webslides"), os.path.join(file_path, folder_name))
        except FileExistsError:
            if force:
                rmtree(os.path.join(file_path, folder_name))
                copytree(os.path.join(os.path.dirname(__file__), "webslides"), os.path.join(file_path, folder_name))
            else:
                raise FileExistsError(f"The file path {os.path.join(file_path, folder_name)} exists, to replace use Presentation.export({file_path}, force=True)")

        # Copy image files
        if os.path.exists("./img"):
            for file_name in os.listdir("./img"):
                if os.path.isfile(os.path.join("img", file_name)):
                    copyfile(os.path.join("img", file_name), os.path.join(file_path, folder_name, "static", "images", file_name))
        elif os.path.exists("./images"):
            for file_name in os.listdir("./images"):
                if os.path.isfile(os.path.join("images", file_name)):
                    copyfile(os.path.join("images", file_name), os.path.join(file_path, folder_name, "static", "images", file_name))

        # replace index.html with generated html
        presentation_content = self.__html__()
        index_path = os.path.join(file_path, folder_name, "index.html")
        print(f"Writing html to {index_path}")
        with open(index_path, "w+") as presentation_file:
            presentation_file.write(presentation_content)

Class variables

var background : str
var description : str
var endcard : bool
var favicon : Union[bool, Image]
var footer : Union[bool, Footer]
var image : Union[bool, Image]
var intro : bool
var navbar : Union[bool, Navbar]
var slides : List[Slide]
var title : str
var url : str
var vertical : bool

Methods

def export(self, file_path: str, folder_name: Union[str, bool] = False, force: bool = False)

Exports the presentation files

Parameters

file_path : (str)
The path to export the folder of content to
folder_name : (str or False)
If you want to overwrite the folder name the output files are put in (defaults to Presentation.title), optional and defaults to False
force : (bool)
Whether to force generating files (overwrite existing files if found), optional and defaults to False

Notes

  • all files are exported to file_path/folder_name
  • folder_name defaults to Presenation.title

Raises

FileExistsError
If force is False, and a folder exists at file_path/folder_name

Examples

Export a presentation to the current directory at /Presentation

from ezprez.core import Presentation
prez = Presentation(title, description, url)

# Export the files to the current directory at /Presentation
prez.export(".", force=True, folder_name="Presentation")
Expand source code
def export(self, file_path:str, folder_name:Union[str, bool] = False, force:bool = False):
    """Exports the presentation files

    Parameters
    ----------
    file_path : (str)
        The path to export the folder of content to

    folder_name : (str or False)
        If you want to overwrite the folder name the output files are put in (defaults to Presentation.title), optional and defaults to False

    force : (bool)
        Whether to force generating files (overwrite existing files if found), optional and defaults to False

    Notes
    -----
    - all files are exported to file_path/folder_name
    - folder_name defaults to Presenation.title

    Raises
    ------
    FileExistsError
        If force is False, and a folder exists at file_path/folder_name

    Examples
    --------
    ### Export a presentation to the current directory at /Presentation
    ```
    from ezprez.core import Presentation
    prez = Presentation(title, description, url)

    # Export the files to the current directory at /Presentation
    prez.export(".", force=True, folder_name="Presentation")
    ```
    """
    if not folder_name:
        folder_name = self.title
    file_path = os.path.abspath(file_path)

    # finding downloads folder
    if os.name == "nt":
        DOWNLOAD_FOLDER = f"{os.getenv('USERPROFILE')}\\Downloads"
    else: # PORT: Assuming variable is there for MacOS and Linux installs
        DOWNLOAD_FOLDER = f"{os.getenv('HOME')}/Downloads" 

    # check if webslides is downloaded, if it is use those files, if not download and extract it
    if not os.path.exists(os.path.join(os.path.dirname(__file__), "webslides")):
        try:
            build(ZIPResource("webslides", "https://webslides.tv/webslides-latest.zip", overwrite_agreement=True))
            copytree(os.path.join(DOWNLOAD_FOLDER, "webslides"), os.path.join(os.path.dirname(__file__), "webslides"))
        except PermissionError:
            elevate()
            copytree(os.path.join(DOWNLOAD_FOLDER, "webslides"), os.path.join(os.path.dirname(__file__), "webslides"))
    try:
        copytree(os.path.join(os.path.dirname(__file__), "webslides"), os.path.join(file_path, folder_name))
    except FileExistsError:
        if force:
            rmtree(os.path.join(file_path, folder_name))
            copytree(os.path.join(os.path.dirname(__file__), "webslides"), os.path.join(file_path, folder_name))
        else:
            raise FileExistsError(f"The file path {os.path.join(file_path, folder_name)} exists, to replace use Presentation.export({file_path}, force=True)")

    # Copy image files
    if os.path.exists("./img"):
        for file_name in os.listdir("./img"):
            if os.path.isfile(os.path.join("img", file_name)):
                copyfile(os.path.join("img", file_name), os.path.join(file_path, folder_name, "static", "images", file_name))
    elif os.path.exists("./images"):
        for file_name in os.listdir("./images"):
            if os.path.isfile(os.path.join("images", file_name)):
                copyfile(os.path.join("images", file_name), os.path.join(file_path, folder_name, "static", "images", file_name))

    # replace index.html with generated html
    presentation_content = self.__html__()
    index_path = os.path.join(file_path, folder_name, "index.html")
    print(f"Writing html to {index_path}")
    with open(index_path, "w+") as presentation_file:
        presentation_file.write(presentation_content)
class Slide (heading: str, *contents: Union[str, list, tuple, ezprez.components._Component], background: Union[bool, str] = False, horizontal_alignment: str = 'center', vertical_alignment: str = 'center', image: Union[bool, Image] = False, animation: str = 'fadeIn')

The class that is used to generate slides that are fed into the Presentation object

Class Variables

all: (List[Slide]) Contains all slide instances that have been instatiated, accessed through Slide.all

Attributes

heading : (str)
The slide's title/heading
contents : (str, list, or Component)
An arbitrary number of strings, lists or Component subclasses that make up the contents of the slide
background : (False or str)
The background color of the slide (see notes), optional by default False
image : (False or Image)
An image to use as a background for the slide, optional by default False
horizontal_alignment : (str)
Where to align the slide contents horizontally, optional defaults to 'center'
vertical_alignment : (str)
Where to align the slide contents vertically, optional defaults to 'center'
animation : (str)
Which animation to use for slide transition, optional defaults to 'fadeIn'

Notes

  • Horizontal alignment can be left, right or center
  • Vertical alignment can be top, bottom or center
  • Background colors available can be found at https://webslides.tv/demos/components#slide=27; just make sure to remove 'bg', so for example 'bg-black' becomes 'black'
  • Contents is an unpacked variable, meaning you just specify multiple sets of contents such as Slide(title, 'this is content', ['and', 'so', 'is', 'this'], Button('This to', '#'))
  • Animation can be fadeIn, fadeInUp, zoomIn, slideInLeft, slideInRight
  • You can also add 'slow' to the end of any animation to slow the animation i.e. 'zoomIn slow'

Examples

Create a basic text slide

from ezprez.core import Slide
from ezprez.components import Button

Slide('Here is a text slide', 'and this is the text')

Create a slide with some text, a list, and a button

from ezprez.core import Slide
from ezprez.components import Button

Slide('This is the title...', '... and this is content', ['and', 'so', 'is', 'this'], Button('This to', '#'))
Expand source code
class Slide:
    """The class that is used to generate slides that are fed into the Presentation object

    Class Variables
    ---------------
    all: (List[Slide])
        Contains all slide instances that have been instatiated, accessed through Slide.all

    Attributes
    ----------
    heading: (str)
        The slide's title/heading

    contents: (str, list, or Component)
        An arbitrary number of strings, lists or Component subclasses that make up the contents of the slide

    background: (False or str)
        The background color of the slide (see notes), optional by default False

    image: (False or Image)
        An image to use as a background for the slide, optional by default False

    horizontal_alignment: (str)
        Where to align the slide contents horizontally, optional defaults to 'center'

    vertical_alignment: (str)
        Where to align the slide contents vertically, optional defaults to 'center'
    
    animation: (str)
        Which animation to use for slide transition, optional defaults to 'fadeIn'

    Notes
    -----
    - Horizontal alignment can be left, right or center
    - Vertical alignment can be top, bottom or center
    - Background colors available can be found at https://webslides.tv/demos/components#slide=27; just make sure to remove 'bg', so for example 'bg-black' becomes 'black'
    - Contents is an unpacked variable, meaning you just specify multiple sets of contents such as Slide(title, 'this is content', ['and', 'so', 'is', 'this'], Button('This to', '#'))
    - Animation can be fadeIn, fadeInUp, zoomIn, slideInLeft, slideInRight
    - You can also add 'slow' to the end of any animation to slow the animation i.e. 'zoomIn slow'


    Examples
    --------
    ### Create a basic text slide
    ```
    from ezprez.core import Slide
    from ezprez.components import Button

    Slide('Here is a text slide', 'and this is the text')
    ```

    ### Create a slide with some text, a list, and a button
    ```
    from ezprez.core import Slide
    from ezprez.components import Button

    Slide('This is the title...', '... and this is content', ['and', 'so', 'is', 'this'], Button('This to', '#'))
    ```
    """
    all = []

    def __init__(self, heading:str, *contents:Union[str, list, tuple, _Component], background:Union[bool,str] = False, horizontal_alignment:str = "center", vertical_alignment:str = "center", image:Union[bool, Image]=False, animation:str = "fadeIn" ):
        # Assign class attributes
        self.image = image
        self.heading = heading
        self.contents = contents
        self.background = background
        self.vertical_alignment = vertical_alignment 
        self.horizontal_alignment = horizontal_alignment
        self.animation = animation

        # Append slide to the class variable Slide.all
        Slide.all.append(self)

    def _generate_content(self):
        """Generates the necessary html with the provided contents"""
        result = f"\n\t\t\t<section class='bg-{self.background} slide-{self.vertical_alignment}'>"
        
        if self.image:
            result += f"\n\t\t\t\t<span class='background' style='background-image:url(\"./static/images/{self.image.filename}\")'></span>"
        result += f"\n\t\t\t\t<div class='wrap {self.animation}'>\n\t\t\t\t\t<div class='content-{self.horizontal_alignment}'>\n\t\t\t\t\t<h2>{self.heading}</h2>\n"
        
        for content in self.contents:
            if type(content) == str: # If the current peice of content is a str
                result += f"\t\t\t\t\t<p>{content}</p>\n"

            elif type(content) == list or type(content) == tuple: # If the current peice of content is a list or tuple
                result += f"\t\t\t\t\t<div class='grid {'content-' + self.horizontal_alignment if not self.horizontal_alignment == 'right' else ''}'>\n\t\t\t\t\t\t\t<ul style='text-align:justify;'>\n"
                for bullet_point in content: # Iterate through each element in the list/tuple
                    result += f"\t\t\t\t\t\t\t\t<li>{bullet_point}</li>\n"
                result += "\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</ul>\n"
            elif isinstance(content, _Component) or type(SocialLink): # If the current peice of content is a Component
                result += content.__html__()
        result += "\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</section>\n"

        yield result


    def __html__(self):
        """Generates the markup for each slide"""
        result = ""
        for content in self._generate_content():
            result+= content
        return result

Class variables

var all